forwarding context options in user.verify

change original "options" argument to "verifyOptions"
adds ctx options argument as "options"
forward context "options" down to relevant downstream functions
review verifyOptions assertions
code cleaning
tests code cleaning
This commit is contained in:
ebarault 2017-04-07 20:31:11 +02:00
parent 31e22d3b3f
commit 912aad8b35
2 changed files with 288 additions and 266 deletions

View File

@ -437,18 +437,18 @@ module.exports = function(User) {
* Verify a user's identity by sending them a confirmation email.
*
* ```js
* var options = {
* var verifyOptions = {
* type: 'email',
* to: user.email,
* from: noreply@example.com,
* template: 'verify.ejs',
* redirect: '/',
* tokenGenerator: function (user, cb) { cb("random-token"); }
* };
*
* user.verify(options, next);
* user.verify(verifyOptions, options, next);
* ```
*
* @options {Object} options
* @options {Object} verifyOptions
* @property {String} type Must be 'email'.
* @property {String} to Email address to which verification email is sent.
* @property {String} from Sender email addresss, for example
@ -468,130 +468,157 @@ module.exports = function(User) {
* 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.
* @callback {Function} fn Callback function.
* @param {Object} options remote context options.
* @callback {Function} cb Callback function.
* @param {Error} err Error object.
* @param {Object} object Contains email, token, uid.
* @promise
*/
User.prototype.verify = function(options, fn) {
fn = fn || utils.createPromiseCallback();
User.prototype.verify = function(verifyOptions, options, cb) {
if (cb === undefined && typeof options === 'function') {
cb = options;
options = undefined;
}
cb = cb || utils.createPromiseCallback();
var user = this;
var userModel = this.constructor;
var registry = userModel.registry;
var pkName = userModel.definition.idName() || 'id';
assert(typeof options === 'object', 'options required when calling user.verify()');
assert(options.type, 'You must supply a verification type (options.type)');
assert(options.type === 'email', 'Unsupported verification type');
assert(options.to || this.email,
'Must include options.to when calling user.verify() ' +
'or the user must have an email property');
assert(options.from, 'Must include options.from when calling user.verify()');
options.redirect = options.redirect || '/';
// final assertion is performed once all options are assigned
assert(typeof verifyOptions === 'object',
'verifyOptions object param required when calling user.verify()');
// Set a default template generation function if none provided
verifyOptions.templateFn = verifyOptions.templateFn || createVerificationEmailBody;
// Set a default token generation function if none provided
verifyOptions.generateVerificationToken = verifyOptions.generateVerificationToken ||
User.generateVerificationToken;
// Set a default mailer function if none provided
verifyOptions.mailer = verifyOptions.mailer || userModel.email ||
registry.getModelByType(loopback.Email);
var pkName = userModel.definition.idName() || 'id';
verifyOptions.redirect = verifyOptions.redirect || '/';
var defaultTemplate = path.join(__dirname, '..', '..', 'templates', 'verify.ejs');
options.template = path.resolve(options.template || defaultTemplate);
options.user = this;
options.protocol = options.protocol || 'http';
verifyOptions.template = path.resolve(verifyOptions.template || defaultTemplate);
verifyOptions.user = user;
verifyOptions.protocol = verifyOptions.protocol || 'http';
var app = userModel.app;
options.host = options.host || (app && app.get('host')) || 'localhost';
options.port = options.port || (app && app.get('port')) || 3000;
options.restApiRoot = options.restApiRoot || (app && app.get('restApiRoot')) || '/api';
verifyOptions.host = verifyOptions.host || (app && app.get('host')) || 'localhost';
verifyOptions.port = verifyOptions.port || (app && app.get('port')) || 3000;
verifyOptions.restApiRoot = verifyOptions.restApiRoot || (app && app.get('restApiRoot')) || '/api';
var displayPort = (
(options.protocol === 'http' && options.port == '80') ||
(options.protocol === 'https' && options.port == '443')
) ? '' : ':' + options.port;
(verifyOptions.protocol === 'http' && verifyOptions.port == '80') ||
(verifyOptions.protocol === 'https' && verifyOptions.port == '443')
) ? '' : ':' + verifyOptions.port;
var urlPath = joinUrlPath(
options.restApiRoot,
verifyOptions.restApiRoot,
userModel.http.path,
userModel.sharedClass.findMethodByName('confirm').http.path
);
options.verifyHref = options.verifyHref ||
options.protocol +
verifyOptions.verifyHref = verifyOptions.verifyHref ||
verifyOptions.protocol +
'://' +
options.host +
verifyOptions.host +
displayPort +
urlPath +
'?' + qs.stringify({
uid: '' + options.user[pkName],
redirect: options.redirect,
uid: '' + verifyOptions.user[pkName],
redirect: verifyOptions.redirect,
});
options.templateFn = options.templateFn || createVerificationEmailBody;
verifyOptions.to = verifyOptions.to || user.email;
verifyOptions.subject = verifyOptions.subject || g.f('Thanks for Registering');
verifyOptions.headers = verifyOptions.headers || {};
// Email model
var Email =
options.mailer || this.constructor.email || registry.getModelByType(loopback.Email);
// assert the verifyOptions params that might have been badly defined
assertVerifyOptions(verifyOptions);
// Set a default token generation function if one is not provided
var tokenGenerator = options.generateVerificationToken || User.generateVerificationToken;
assert(typeof tokenGenerator === 'function', 'generateVerificationToken must be a function');
tokenGenerator(user, function(err, token) {
if (err) { return fn(err); }
// argument "options" is passed depending on verifyOptions.generateVerificationToken function requirements
var tokenGenerator = verifyOptions.generateVerificationToken;
if (tokenGenerator.length == 3) {
tokenGenerator(user, options, addTokenToUserAndSave);
} else {
tokenGenerator(user, addTokenToUserAndSave);
}
function addTokenToUserAndSave(err, token) {
if (err) return cb(err);
user.verificationToken = token;
user.save(function(err) {
if (err) {
fn(err);
} else {
sendEmail(user);
}
user.save(options, function(err) {
if (err) return cb(err);
sendEmail(user);
});
});
}
// TODO - support more verification types
function sendEmail(user) {
options.verifyHref += '&token=' + user.verificationToken;
verifyOptions.verifyHref += '&token=' + user.verificationToken;
verifyOptions.verificationToken = user.verificationToken;
options.verificationToken = user.verificationToken;
verifyOptions.text = verifyOptions.text || g.f('Please verify your email by opening ' +
'this link in a web browser:\n\t%s', verifyOptions.verifyHref);
options.text = options.text || g.f('Please verify your email by opening ' +
'this link in a web browser:\n\t%s', options.verifyHref);
verifyOptions.text = verifyOptions.text.replace(/\{href\}/g, verifyOptions.verifyHref);
options.text = options.text.replace(/\{href\}/g, options.verifyHref);
// argument "options" is passed depending on templateFn function requirements
var templateFn = verifyOptions.templateFn;
if (templateFn.length == 3) {
templateFn(verifyOptions, options, setHtmlContentAndSend);
} else {
templateFn(verifyOptions, setHtmlContentAndSend);
}
options.to = options.to || user.email;
function setHtmlContentAndSend(err, html) {
if (err) return cb(err);
options.subject = options.subject || g.f('Thanks for Registering');
verifyOptions.html = html;
options.headers = options.headers || {};
options.templateFn(options, function(err, html) {
if (err) {
fn(err);
} else {
setHtmlContentAndSend(html);
}
});
function setHtmlContentAndSend(html) {
options.html = html;
// Remove options.template to prevent rejection by certain
// Remove verifyOptions.template to prevent rejection by certain
// nodemailer transport plugins.
delete options.template;
delete verifyOptions.template;
Email.send(options, function(err, email) {
if (err) {
fn(err);
} else {
fn(null, {email: email, token: user.verificationToken, uid: user[pkName]});
}
});
// argument "options" is passed depending on Email.send function requirements
var Email = verifyOptions.mailer;
if (Email.send.length == 3) {
Email.send(verifyOptions, options, handleAfterSend);
} else {
Email.send(verifyOptions, handleAfterSend);
}
function handleAfterSend(err, email) {
if (err) return cb(err);
cb(null, {email: email, token: user.verificationToken, uid: user[pkName]});
}
}
}
return fn.promise;
return cb.promise;
};
function createVerificationEmailBody(options, cb) {
var template = loopback.template(options.template);
var body = template(options);
function assertVerifyOptions(verifyOptions) {
assert(verifyOptions.type, 'You must supply a verification type (verifyOptions.type)');
assert(verifyOptions.type === 'email', 'Unsupported verification type');
assert(verifyOptions.to, 'Must include verifyOptions.to when calling user.verify() ' +
'or the user must have an email property');
assert(verifyOptions.from, 'Must include verifyOptions.from when calling user.verify()');
assert(typeof verifyOptions.templateFn === 'function',
'templateFn must be a function');
assert(typeof verifyOptions.generateVerificationToken === 'function',
'generateVerificationToken must be a function');
assert(verifyOptions.mailer, 'A mailer function must be provided');
assert(typeof verifyOptions.mailer.send === 'function', 'mailer.send must be a function ');
}
function createVerificationEmailBody(verifyOptions, options, cb) {
var template = loopback.template(verifyOptions.template);
var body = template(verifyOptions);
cb(null, body);
}
@ -603,9 +630,10 @@ module.exports = function(User) {
* 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
* @param {object} options remote context options.
* @param {Function} cb The generator must pass back the new token with this function call.
*/
User.generateVerificationToken = function(user, cb) {
User.generateVerificationToken = function(user, options, cb) {
crypto.randomBytes(64, function(err, buf) {
cb(err, buf && buf.toString('hex'));
});

View File

@ -8,10 +8,12 @@ var assert = require('assert');
var expect = require('./helpers/expect');
var request = require('supertest');
var loopback = require('../');
var User, AccessToken;
var async = require('async');
var url = require('url');
var extend = require('util')._extend;
var Promise = require('bluebird');
var User, AccessToken;
describe('User', function() {
this.timeout(10000);
@ -30,14 +32,14 @@ describe('User', function() {
var validMixedCaseEmailCredentials = {email: 'Foo@bar.com', password: 'bar'};
var invalidCredentials = {email: 'foo1@bar.com', password: 'invalid'};
var incompleteCredentials = {password: 'bar1'};
var validCredentialsUser, validCredentialsEmailVerifiedUser;
var validCredentialsUser, validCredentialsEmailVerifiedUser, user;
// Create a local app variable to prevent clashes with the global
// variable shared by all tests. While this should not be necessary if
// the tests were written correctly, it turns out that's not the case :(
var app = null;
beforeEach(function setupAppAndModels(done) {
beforeEach(function setupAppAndModels() {
// override the global app object provided by test/support.js
// and create a local one that does not share state with other tests
app = loopback({localRegistry: true, loadBuiltinModels: true});
@ -90,14 +92,13 @@ describe('User', function() {
app.use(loopback.token({model: AccessToken}));
app.use(loopback.rest());
User.create(validCredentials, function(err, user) {
if (err) return done(err);
validCredentialsUser = user;
User.create(validCredentialsEmailVerified, function(err, user) {
if (err) return done(err);
validCredentialsEmailVerifiedUser = user;
done();
});
// create 2 users: with and without verified email
return Promise.map(
[validCredentials, validCredentialsEmailVerified],
credentials => User.create(credentials)
).then(users => {
validCredentialsUser = user = users[0];
validCredentialsEmailVerifiedUser = users[1];
});
});
@ -1405,21 +1406,23 @@ describe('User', function() {
});
describe('Verification', function() {
describe('user.verify(options, fn)', function() {
describe('user.verify(verifyOptions, options, cb)', function() {
const ctxOptions = {testFlag: true};
let verifyOptions;
beforeEach(function() {
// reset verifyOptions before each test
verifyOptions = {
type: 'email',
from: 'noreply@example.org',
};
});
it('Verify a user\'s email address', 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'),
};
user.verify(options, function(err, result) {
user.verify(verifyOptions, function(err, result) {
assert(result.email);
assert(result.email.response);
assert(result.token);
@ -1445,16 +1448,7 @@ describe('User', function() {
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'),
};
user.verify(options)
user.verify(verifyOptions)
.then(function(result) {
assert(result.email);
assert(result.email.response);
@ -1484,17 +1478,9 @@ describe('User', function() {
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'),
headers: {'message-id': 'custom-header-value'},
};
verifyOptions.headers = {'message-id': 'custom-header-value'};
user.verify(options, function(err, result) {
user.verify(verifyOptions, function(err, result) {
assert(result.email);
assert.equal(result.email.messageId, 'custom-header-value');
@ -1516,19 +1502,11 @@ describe('User', function() {
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'),
templateFn: function(options, cb) {
cb(null, 'custom template - verify url: ' + options.verifyHref);
},
verifyOptions.templateFn = function(verifyOptions, cb) {
cb(null, 'custom template - verify url: ' + verifyOptions.verifyHref);
};
user.verify(options, function(err, result) {
user.verify(verifyOptions, function(err, result) {
assert(result.email);
assert(result.email.response);
assert(result.token);
@ -1558,17 +1536,9 @@ describe('User', function() {
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'),
templateFn: function(options, cb) {
actualVerifyHref = options.verifyHref;
cb(null, 'dummy body');
},
verifyOptions.templateFn = function(verifyOptions, cb) {
actualVerifyHref = verifyOptions.verifyHref;
cb(null, 'dummy body');
};
// replace the string id with an object
@ -1579,7 +1549,7 @@ describe('User', function() {
});
user.pk = {toString: function() { return idString; }};
user.verify(options, function(err, result) {
user.verify(verifyOptions, function(err, result) {
expect(result.uid).to.exist().and.be.an('object');
expect(result.uid.toString()).to.equal(idString);
const parsed = url.parse(actualVerifyHref, true);
@ -1602,26 +1572,18 @@ describe('User', function() {
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');
});
},
verifyOptions.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) {
user.verify(verifyOptions, function(err, result) {
assert(result.email);
assert(result.email.response);
assert(result.token);
@ -1647,22 +1609,14 @@ describe('User', function() {
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'));
});
},
verifyOptions.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) {
user.verify(verifyOptions, function(err, result) {
assert(err);
assert.equal(err.message, 'Fake error');
assert.equal(result, undefined);
@ -1686,17 +1640,12 @@ describe('User', function() {
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: 'http',
Object.assign(verifyOptions, {
host: 'myapp.org',
port: 3000,
};
});
user.verify(options, function(err, result) {
user.verify(verifyOptions, function(err, result) {
var msg = result.email.response.toString('utf-8');
assert(~msg.indexOf('http://myapp.org:3000/'));
@ -1718,17 +1667,12 @@ describe('User', function() {
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: 'http',
Object.assign(verifyOptions, {
host: 'myapp.org',
port: 80,
};
});
user.verify(options, function(err, result) {
user.verify(verifyOptions, function(err, result) {
var msg = result.email.response.toString('utf-8');
assert(~msg.indexOf('http://myapp.org/'));
@ -1750,17 +1694,13 @@ describe('User', function() {
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: 'https',
Object.assign(verifyOptions, {
host: 'myapp.org',
port: 3000,
};
protocol: 'https',
});
user.verify(options, function(err, result) {
user.verify(verifyOptions, function(err, result) {
var msg = result.email.response.toString('utf-8');
assert(~msg.indexOf('https://myapp.org:3000/'));
@ -1782,17 +1722,13 @@ describe('User', function() {
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: 'https',
Object.assign(verifyOptions, {
host: 'myapp.org',
protocol: 'https',
port: 443,
};
});
user.verify(options, function(err, result) {
user.verify(verifyOptions, function(err, result) {
var msg = result.email.response.toString('utf-8');
assert(~msg.indexOf('https://myapp.org/'));
@ -1828,17 +1764,13 @@ describe('User', function() {
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: '/',
Object.assign(verifyOptions, {
host: 'myapp.org',
port: 3000,
restApiRoot: '/',
};
});
user.verify(options, function(err, result) {
user.verify(verifyOptions, function(err, result) {
if (err) return next(err);
emailBody = result.email.response.toString('utf-8');
next();
@ -1858,40 +1790,31 @@ describe('User', function() {
});
});
it('removes "options.template" from Email payload', function() {
it('removes "verifyOptions.template" from Email payload', function() {
var MailerMock = {
send: function(options, cb) { cb(null, options); },
send: function(verifyOptions, cb) { cb(null, verifyOptions); },
};
verifyOptions.mailer = MailerMock;
return User.create({email: 'user@example.com', password: 'pass'})
.then(function(user) {
return user.verify({
type: 'email',
from: 'noreply@example.com',
mailer: MailerMock,
});
})
return user.verify(verifyOptions)
.then(function(result) {
expect(result.email).to.not.have.property('template');
});
});
it('allows hash fragment in redirectUrl', function() {
return User.create({email: 'test@example.com', password: 'pass'})
.then(user => {
let actualVerifyHref;
return user.verify({
type: 'email',
to: user.email,
from: 'noreply@myapp.org',
redirect: '#/some-path?a=1&b=2',
templateFn: (options, cb) => {
actualVerifyHref = options.verifyHref;
cb(null, 'dummy body');
},
})
.then(() => actualVerifyHref);
})
let actualVerifyHref;
Object.assign(verifyOptions, {
redirect: '#/some-path?a=1&b=2',
templateFn: (verifyOptions, cb) => {
actualVerifyHref = verifyOptions.verifyHref;
cb(null, 'dummy body');
},
});
return user.verify(verifyOptions)
.then(() => actualVerifyHref)
.then(verifyHref => {
var parsed = url.parse(verifyHref, true);
expect(parsed.query.redirect, 'redirect query')
@ -1899,36 +1822,107 @@ describe('User', function() {
});
});
it('verify that options.templateFn receives options.verificationToken', function() {
return User.create({email: 'test1@example.com', password: 'pass'})
.then(user => {
let actualVerificationToken;
return user.verify({
type: 'email',
to: user.email,
from: 'noreply@myapp.org',
redirect: '#/some-path?a=1&b=2',
templateFn: (options, cb) => {
actualVerificationToken = options.verificationToken;
cb(null, 'dummy body');
},
})
.then(() => actualVerificationToken);
})
it('verifies that verifyOptions.templateFn receives verifyOptions.verificationToken',
function() {
let actualVerificationToken;
Object.assign(verifyOptions, {
redirect: '#/some-path?a=1&b=2',
templateFn: (verifyOptions, cb) => {
actualVerificationToken = verifyOptions.verificationToken;
cb(null, 'dummy body');
},
});
return user.verify(verifyOptions)
.then(() => actualVerificationToken)
.then(token => {
expect(token).to.exist();
});
});
it('forwards the "options" argument to user.save() ' +
'when adding verification token', function() {
let onBeforeSaveCtx = {};
// before save operation hook to capture remote ctx when saving
// verification token in user instance
User.observe('before save', function(ctx, next) {
onBeforeSaveCtx = ctx || {};
next();
});
return user.verify(verifyOptions, ctxOptions)
.then(() => {
// not checking equality since other properties are added by user.save()
expect(onBeforeSaveCtx.options).to.contain({testFlag: true});
});
});
it('forwards the "options" argument to a custom templateFn function', function() {
let templateFnOptions;
// custom templateFn function accepting the options argument
verifyOptions.templateFn = (verifyOptions, options, cb) => {
templateFnOptions = options;
cb(null, 'dummy body');
};
return user.verify(verifyOptions, ctxOptions)
.then(() => {
// not checking equality since other properties are added by user.save()
expect(templateFnOptions).to.contain({testFlag: true});
});
});
it('forwards the "options" argment to a custom token generator function', function() {
let generateTokenOptions;
// custom generateVerificationToken function accepting the options argument
verifyOptions.generateVerificationToken = (user, options, cb) => {
generateTokenOptions = options;
cb(null, 'dummy token');
};
return user.verify(verifyOptions, ctxOptions)
.then(() => {
// not checking equality since other properties are added by user.save()
expect(generateTokenOptions).to.contain({testFlag: true});
});
});
it('forwards the "options" argument to a custom mailer function', function() {
let mailerOptions;
// custom mailer function accepting the options argument
const mailer = function() {};
mailer.send = function(verifyOptions, options, cb) {
mailerOptions = options;
cb(null, 'dummy result');
};
verifyOptions.mailer = mailer;
return user.verify(verifyOptions, ctxOptions)
.then(() => {
// not checking equality since other properties are added by user.save()
expect(mailerOptions).to.contain({testFlag: true});
});
});
function givenUser() {
return User.create({email: 'test@example.com', password: 'pass'})
.then(u => user = u);
}
});
describe('User.confirm(options, fn)', function() {
var options;
var verifyOptions;
function testConfirm(testFunc, done) {
User.afterRemote('create', function(ctx, user, next) {
assert(user, 'afterRemote should include result');
options = {
verifyOptions = {
type: 'email',
to: user.email,
from: 'noreply@myapp.org',
@ -1937,7 +1931,7 @@ describe('User', function() {
host: ctx.req.get('host'),
};
user.verify(options, function(err, result) {
user.verify(verifyOptions, function(err, result) {
if (err) return done(err);
testFunc(result, done);
@ -1959,7 +1953,7 @@ describe('User', function() {
request(app)
.get('/test-users/confirm?uid=' + (result.uid) +
'&token=' + encodeURIComponent(result.token) +
'&redirect=' + encodeURIComponent(options.redirect))
'&redirect=' + encodeURIComponent(verifyOptions.redirect))
.expect(302)
.end(function(err, res) {
if (err) return done(err);
@ -2011,7 +2005,7 @@ describe('User', function() {
request(app)
.get('/test-users/confirm?uid=' + (result.uid + '_invalid') +
'&token=' + encodeURIComponent(result.token) +
'&redirect=' + encodeURIComponent(options.redirect))
'&redirect=' + encodeURIComponent(verifyOptions.redirect))
.expect(404)
.end(function(err, res) {
if (err) return done(err);
@ -2030,7 +2024,7 @@ describe('User', function() {
request(app)
.get('/test-users/confirm?uid=' + result.uid +
'&token=' + encodeURIComponent(result.token) + '_invalid' +
'&redirect=' + encodeURIComponent(options.redirect))
'&redirect=' + encodeURIComponent(verifyOptions.redirect))
.expect(400)
.end(function(err, res) {
if (err) return done(err);