Merge pull request #1205 from strongloop/feature/custom-verify-token-generator
Add ability to pass in custom verification token generator
This commit is contained in:
commit
911d8323b4
|
@ -303,11 +303,12 @@ module.exports = function(User) {
|
||||||
*
|
*
|
||||||
* ```js
|
* ```js
|
||||||
* var options = {
|
* var options = {
|
||||||
* type: 'email',
|
* type: 'email',
|
||||||
* to: user.email,
|
* to: user.email,
|
||||||
* template: 'verify.ejs',
|
* template: 'verify.ejs',
|
||||||
* redirect: '/'
|
* redirect: '/',
|
||||||
* };
|
* tokenGenerator: function (user, cb) { cb("random-token"); }
|
||||||
|
* };
|
||||||
*
|
*
|
||||||
* user.verify(options, next);
|
* user.verify(options, next);
|
||||||
* ```
|
* ```
|
||||||
|
@ -323,6 +324,11 @@ module.exports = function(User) {
|
||||||
* page, for example, `'verify.ejs'.
|
* page, for example, `'verify.ejs'.
|
||||||
* @property {String} redirect Page to which user will be redirected after
|
* @property {String} redirect Page to which user will be redirected after
|
||||||
* they verify their email, for example `'/'` for root URI.
|
* they verify their email, for example `'/'` for root URI.
|
||||||
|
* @property {Function} generateVerificationToken A function to be used to
|
||||||
|
* generate the verification token. It must accept the user object and a
|
||||||
|
* callback function. This function should NOT add the token to the user
|
||||||
|
* object, instead simply execute the callback with the token! User saving
|
||||||
|
* and email sending will be handled in the `verify()` method.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
User.prototype.verify = function(options, fn) {
|
User.prototype.verify = function(options, fn) {
|
||||||
|
@ -360,19 +366,20 @@ module.exports = function(User) {
|
||||||
// Email model
|
// Email model
|
||||||
var Email = options.mailer || this.constructor.email || loopback.getModelByType(loopback.Email);
|
var Email = options.mailer || this.constructor.email || loopback.getModelByType(loopback.Email);
|
||||||
|
|
||||||
crypto.randomBytes(64, function(err, buf) {
|
// Set a default token generation function if one is not provided
|
||||||
if (err) {
|
var tokenGenerator = options.generateVerificationToken || User.generateVerificationToken;
|
||||||
fn(err);
|
|
||||||
} else {
|
tokenGenerator(user, function(err, token) {
|
||||||
user.verificationToken = buf.toString('hex');
|
if (err) { return fn(err); }
|
||||||
user.save(function(err) {
|
|
||||||
if (err) {
|
user.verificationToken = token;
|
||||||
fn(err);
|
user.save(function(err) {
|
||||||
} else {
|
if (err) {
|
||||||
sendEmail(user);
|
fn(err);
|
||||||
}
|
} else {
|
||||||
});
|
sendEmail(user);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO - support more verification types
|
// TODO - support more verification types
|
||||||
|
@ -401,6 +408,22 @@ module.exports = function(User) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A default verification token generator which accepts the user the token is
|
||||||
|
* being generated for and a callback function to indicate completion.
|
||||||
|
* This one uses the crypto library and 64 random bytes (converted to hex)
|
||||||
|
* for the token. When used in combination with the user.verify() method this
|
||||||
|
* function will be called with the `user` object as it's context (`this`).
|
||||||
|
*
|
||||||
|
* @param {object} user The User this token is being generated for.
|
||||||
|
* @param {Function} cb The generator must pass back the new token with this function call
|
||||||
|
*/
|
||||||
|
User.generateVerificationToken = function(user, cb) {
|
||||||
|
crypto.randomBytes(64, function(err, buf) {
|
||||||
|
cb(err, buf && buf.toString('hex'));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Confirm the user's identity.
|
* Confirm the user's identity.
|
||||||
*
|
*
|
||||||
|
|
|
@ -842,6 +842,91 @@ describe('User', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Verify a user\'s email address with custom token generator', function(done) {
|
||||||
|
User.afterRemote('create', function(ctx, user, next) {
|
||||||
|
assert(user, 'afterRemote should include result');
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
type: 'email',
|
||||||
|
to: user.email,
|
||||||
|
from: 'noreply@myapp.org',
|
||||||
|
redirect: '/',
|
||||||
|
protocol: ctx.req.protocol,
|
||||||
|
host: ctx.req.get('host'),
|
||||||
|
generateVerificationToken: function(user, cb) {
|
||||||
|
assert(user);
|
||||||
|
assert.equal(user.email, 'bar@bat.com');
|
||||||
|
assert(cb);
|
||||||
|
assert.equal(typeof cb, 'function');
|
||||||
|
// let's ensure async execution works on this one
|
||||||
|
process.nextTick(function() {
|
||||||
|
cb(null, 'token-123456');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
user.verify(options, function(err, result) {
|
||||||
|
assert(result.email);
|
||||||
|
assert(result.email.response);
|
||||||
|
assert(result.token);
|
||||||
|
assert.equal(result.token, 'token-123456');
|
||||||
|
var msg = result.email.response.toString('utf-8');
|
||||||
|
assert(~msg.indexOf('token-123456'));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
request(app)
|
||||||
|
.post('/users')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.send({email: 'bar@bat.com', password: 'bar'})
|
||||||
|
.end(function(err, res) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Fails if custom token generator returns error', function(done) {
|
||||||
|
User.afterRemote('create', function(ctx, user, next) {
|
||||||
|
assert(user, 'afterRemote should include result');
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
type: 'email',
|
||||||
|
to: user.email,
|
||||||
|
from: 'noreply@myapp.org',
|
||||||
|
redirect: '/',
|
||||||
|
protocol: ctx.req.protocol,
|
||||||
|
host: ctx.req.get('host'),
|
||||||
|
generateVerificationToken: function(user, cb) {
|
||||||
|
// let's ensure async execution works on this one
|
||||||
|
process.nextTick(function() {
|
||||||
|
cb(new Error('Fake error'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
user.verify(options, function(err, result) {
|
||||||
|
assert(err);
|
||||||
|
assert.equal(err.message, 'Fake error');
|
||||||
|
assert.equal(result, undefined);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
request(app)
|
||||||
|
.post('/users')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.send({email: 'bar@bat.com', password: 'bar'})
|
||||||
|
.end(function(err, res) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('User.confirm(options, fn)', function() {
|
describe('User.confirm(options, fn)', function() {
|
||||||
|
|
Loading…
Reference in New Issue