Add password reset
This commit is contained in:
parent
2090959bfc
commit
e92c46a4e4
12
docs/api.md
12
docs/api.md
|
@ -1,8 +1,8 @@
|
||||||
## Node.js API
|
## Node.js API
|
||||||
|
|
||||||
* [App](#app-object)
|
* [App](api-app.md)
|
||||||
* [DataSource](#data-source-object)
|
* [Model](api-model.md)
|
||||||
* [GeoPoint](#geopoint-object)
|
* [Remote methods and hooks](api-model-remote.md)
|
||||||
* [Model](#model-object)
|
* [DataSource](api-datasource.md)
|
||||||
* [Remote methods and hooks](#remote-methods-and-hooks)
|
* [GeoPoint](api-geopoint.md)
|
||||||
* [REST API](#rest-api)
|
* [REST API](rest.md)
|
||||||
|
|
|
@ -147,31 +147,62 @@ User.afterRemote('create', function(ctx, user, next) {
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Send Reset Password Email
|
### Reset Password
|
||||||
|
|
||||||
Send an email to the user's supplied email address containing a link to reset their password.
|
You can implement password reset using the `User.resetPassword` method.
|
||||||
|
|
||||||
|
Request a password reset access token.
|
||||||
|
|
||||||
|
**Node.js**
|
||||||
|
|
||||||
```js
|
```js
|
||||||
User.reset(email, function(err) {
|
User.resetPassword({
|
||||||
console.log('email sent');
|
email: 'foo@bar.com'
|
||||||
|
}, function () {
|
||||||
|
console.log('ready to change password');
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Remote Password Reset
|
**REST**
|
||||||
|
|
||||||
The password reset email will send users to a page rendered by loopback with fields required to reset the user's password. You may customize this template by defining a `resetTemplate` setting.
|
```
|
||||||
|
POST
|
||||||
|
|
||||||
```js
|
/users/reset-password
|
||||||
User.settings.resetTemplate = 'reset.ejs';
|
...
|
||||||
|
{
|
||||||
|
"email": "foo@bar.com"
|
||||||
|
}
|
||||||
|
...
|
||||||
|
200 OK
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Remote Password Reset Confirmation
|
You must the handle the `resetPasswordRequest` event this on the server to
|
||||||
|
send a reset email containing an access token to the correct user. The
|
||||||
|
example below shows a basic setup for sending the reset email.
|
||||||
|
|
||||||
Confirm the password reset.
|
```
|
||||||
|
User.on('resetPasswordRequest', function (info) {
|
||||||
|
console.log(info.email); // the email of the requested user
|
||||||
|
console.log(info.accessToken.id); // the temp access token to allow password reset
|
||||||
|
|
||||||
```js
|
// requires AccessToken.belongsTo(User)
|
||||||
User.confirmReset(token, function(err) {
|
info.accessToken.user(function (err, user) {
|
||||||
console.log(err || 'your password was reset');
|
console.log(user); // the actual user
|
||||||
|
var emailData = {
|
||||||
|
user: user,
|
||||||
|
accessToken: accessToken
|
||||||
|
};
|
||||||
|
|
||||||
|
// this email should include a link to a page with a form to
|
||||||
|
// change the password using the access token in the email
|
||||||
|
Email.send({
|
||||||
|
to: user.email,
|
||||||
|
subject: 'Reset Your Password',
|
||||||
|
text: loopback.template('reset-template.txt.ejs')(emailData),
|
||||||
|
html: loopback.template('reset-template.html.ejs')(emailData)
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ var Model = require('../loopback').Model
|
||||||
, LocalStrategy = require('passport-local').Strategy
|
, LocalStrategy = require('passport-local').Strategy
|
||||||
, BaseAccessToken = require('./access-token')
|
, BaseAccessToken = require('./access-token')
|
||||||
, DEFAULT_TTL = 1209600 // 2 weeks in seconds
|
, DEFAULT_TTL = 1209600 // 2 weeks in seconds
|
||||||
|
, DEFAULT_RESET_PW_TTL = 15 * 60 // 15 mins in seconds
|
||||||
, DEFAULT_MAX_TTL = 31556926; // 1 year in seconds
|
, DEFAULT_MAX_TTL = 31556926; // 1 year in seconds
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -235,6 +236,42 @@ User.confirm = function (uid, token, redirect, fn) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
User.resetPassword = function(options, cb) {
|
||||||
|
var UserModel = this;
|
||||||
|
var ttl = UserModel.settings.resetPasswordTokenTTL || DEFAULT_RESET_PW_TTL;
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
if(typeof options.email === 'string') {
|
||||||
|
UserModel.findOne({email: options.email}, function(err, user) {
|
||||||
|
if(err) {
|
||||||
|
cb(err);
|
||||||
|
} else if(user) {
|
||||||
|
// create a short lived access token for temp login to change password
|
||||||
|
// TODO(ritch) - eventually this should only allow password change
|
||||||
|
user.accessTokens.create({ttl: ttl}, function(err, accessToken) {
|
||||||
|
if(err) {
|
||||||
|
cb(err);
|
||||||
|
} else {
|
||||||
|
cb();
|
||||||
|
UserModel.emit('resetPasswordRequest', {
|
||||||
|
email: options.email,
|
||||||
|
accessToken: accessToken
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
var err = new Error('email is required');
|
||||||
|
err.statusCode = 400;
|
||||||
|
|
||||||
|
cb(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup an extended user model.
|
* Setup an extended user model.
|
||||||
*/
|
*/
|
||||||
|
@ -286,6 +323,16 @@ User.setup = function () {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
loopback.remoteMethod(
|
||||||
|
UserModel.resetPassword,
|
||||||
|
{
|
||||||
|
accepts: [
|
||||||
|
{arg: 'options', type: 'object', required: true, http: {source: 'body'}}
|
||||||
|
],
|
||||||
|
http: {verb: 'post', path: '/reset'}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
UserModel.on('attached', function () {
|
UserModel.on('attached', function () {
|
||||||
UserModel.afterRemote('confirm', function (ctx, inst, next) {
|
UserModel.afterRemote('confirm', function (ctx, inst, next) {
|
||||||
if(ctx.req) {
|
if(ctx.req) {
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
<form>
|
||||||
|
|
||||||
|
</form>
|
|
@ -12,8 +12,8 @@ describe('User', function(){
|
||||||
User.setMaxListeners(0);
|
User.setMaxListeners(0);
|
||||||
|
|
||||||
before(function () {
|
before(function () {
|
||||||
debugger;
|
|
||||||
User.hasMany(AccessToken, {as: 'accessTokens', foreignKey: 'userId'});
|
User.hasMany(AccessToken, {as: 'accessTokens', foreignKey: 'userId'});
|
||||||
|
AccessToken.belongsTo(User);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(function (done) {
|
beforeEach(function (done) {
|
||||||
|
@ -313,4 +313,31 @@ describe('User', function(){
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Password Reset', function () {
|
||||||
|
describe('User.resetPassword(options, cb)', function () {
|
||||||
|
it('Creates a temp accessToken to allow a user to change password', function (done) {
|
||||||
|
var calledBack = false;
|
||||||
|
var email = 'foo@bar.com';
|
||||||
|
|
||||||
|
User.resetPassword({
|
||||||
|
email: email
|
||||||
|
}, function () {
|
||||||
|
calledBack = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
User.once('resetPasswordRequest', function (info) {
|
||||||
|
assert(info.email);
|
||||||
|
assert(info.accessToken);
|
||||||
|
assert(info.accessToken.id);
|
||||||
|
assert.equal(info.accessToken.ttl / 60, 15);
|
||||||
|
assert(calledBack);
|
||||||
|
info.accessToken.user(function (err, user) {
|
||||||
|
assert.equal(user.email, email);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue