Merge pull request #3344 from strongloop/maintenance/passing-context-options-in-user.verify
forward context options in user.verify
This commit is contained in:
commit
faa4975b78
|
@ -437,18 +437,18 @@ module.exports = function(User) {
|
||||||
* Verify a user's identity by sending them a confirmation email.
|
* Verify a user's identity by sending them a confirmation email.
|
||||||
*
|
*
|
||||||
* ```js
|
* ```js
|
||||||
* var options = {
|
* var verifyOptions = {
|
||||||
* type: 'email',
|
* type: 'email',
|
||||||
* to: user.email,
|
* from: noreply@example.com,
|
||||||
* template: 'verify.ejs',
|
* template: 'verify.ejs',
|
||||||
* redirect: '/',
|
* redirect: '/',
|
||||||
* tokenGenerator: function (user, cb) { cb("random-token"); }
|
* 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} type Must be 'email'.
|
||||||
* @property {String} to Email address to which verification email is sent.
|
* @property {String} to Email address to which verification email is sent.
|
||||||
* @property {String} from Sender email addresss, for example
|
* @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
|
* callback function. This function should NOT add the token to the user
|
||||||
* object, instead simply execute the callback with the token! User saving
|
* object, instead simply execute the callback with the token! User saving
|
||||||
* and email sending will be handled in the `verify()` method.
|
* 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 {Error} err Error object.
|
||||||
* @param {Object} object Contains email, token, uid.
|
* @param {Object} object Contains email, token, uid.
|
||||||
* @promise
|
* @promise
|
||||||
*/
|
*/
|
||||||
|
|
||||||
User.prototype.verify = function(options, fn) {
|
User.prototype.verify = function(verifyOptions, options, cb) {
|
||||||
fn = fn || utils.createPromiseCallback();
|
if (cb === undefined && typeof options === 'function') {
|
||||||
|
cb = options;
|
||||||
|
options = undefined;
|
||||||
|
}
|
||||||
|
cb = cb || utils.createPromiseCallback();
|
||||||
|
|
||||||
var user = this;
|
var user = this;
|
||||||
var userModel = this.constructor;
|
var userModel = this.constructor;
|
||||||
var registry = userModel.registry;
|
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');
|
var defaultTemplate = path.join(__dirname, '..', '..', 'templates', 'verify.ejs');
|
||||||
options.template = path.resolve(options.template || defaultTemplate);
|
verifyOptions.template = path.resolve(verifyOptions.template || defaultTemplate);
|
||||||
options.user = this;
|
verifyOptions.user = user;
|
||||||
options.protocol = options.protocol || 'http';
|
verifyOptions.protocol = verifyOptions.protocol || 'http';
|
||||||
|
|
||||||
var app = userModel.app;
|
var app = userModel.app;
|
||||||
options.host = options.host || (app && app.get('host')) || 'localhost';
|
verifyOptions.host = verifyOptions.host || (app && app.get('host')) || 'localhost';
|
||||||
options.port = options.port || (app && app.get('port')) || 3000;
|
verifyOptions.port = verifyOptions.port || (app && app.get('port')) || 3000;
|
||||||
options.restApiRoot = options.restApiRoot || (app && app.get('restApiRoot')) || '/api';
|
verifyOptions.restApiRoot = verifyOptions.restApiRoot || (app && app.get('restApiRoot')) || '/api';
|
||||||
|
|
||||||
var displayPort = (
|
var displayPort = (
|
||||||
(options.protocol === 'http' && options.port == '80') ||
|
(verifyOptions.protocol === 'http' && verifyOptions.port == '80') ||
|
||||||
(options.protocol === 'https' && options.port == '443')
|
(verifyOptions.protocol === 'https' && verifyOptions.port == '443')
|
||||||
) ? '' : ':' + options.port;
|
) ? '' : ':' + verifyOptions.port;
|
||||||
|
|
||||||
var urlPath = joinUrlPath(
|
var urlPath = joinUrlPath(
|
||||||
options.restApiRoot,
|
verifyOptions.restApiRoot,
|
||||||
userModel.http.path,
|
userModel.http.path,
|
||||||
userModel.sharedClass.findMethodByName('confirm').http.path
|
userModel.sharedClass.findMethodByName('confirm').http.path
|
||||||
);
|
);
|
||||||
|
|
||||||
options.verifyHref = options.verifyHref ||
|
verifyOptions.verifyHref = verifyOptions.verifyHref ||
|
||||||
options.protocol +
|
verifyOptions.protocol +
|
||||||
'://' +
|
'://' +
|
||||||
options.host +
|
verifyOptions.host +
|
||||||
displayPort +
|
displayPort +
|
||||||
urlPath +
|
urlPath +
|
||||||
'?' + qs.stringify({
|
'?' + qs.stringify({
|
||||||
uid: '' + options.user[pkName],
|
uid: '' + verifyOptions.user[pkName],
|
||||||
redirect: options.redirect,
|
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
|
// assert the verifyOptions params that might have been badly defined
|
||||||
var Email =
|
assertVerifyOptions(verifyOptions);
|
||||||
options.mailer || this.constructor.email || registry.getModelByType(loopback.Email);
|
|
||||||
|
|
||||||
// Set a default token generation function if one is not provided
|
// argument "options" is passed depending on verifyOptions.generateVerificationToken function requirements
|
||||||
var tokenGenerator = options.generateVerificationToken || User.generateVerificationToken;
|
var tokenGenerator = verifyOptions.generateVerificationToken;
|
||||||
assert(typeof tokenGenerator === 'function', 'generateVerificationToken must be a function');
|
if (tokenGenerator.length == 3) {
|
||||||
|
tokenGenerator(user, options, addTokenToUserAndSave);
|
||||||
tokenGenerator(user, function(err, token) {
|
} else {
|
||||||
if (err) { return fn(err); }
|
tokenGenerator(user, addTokenToUserAndSave);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addTokenToUserAndSave(err, token) {
|
||||||
|
if (err) return cb(err);
|
||||||
user.verificationToken = token;
|
user.verificationToken = token;
|
||||||
user.save(function(err) {
|
user.save(options, function(err) {
|
||||||
if (err) {
|
if (err) return cb(err);
|
||||||
fn(err);
|
sendEmail(user);
|
||||||
} else {
|
|
||||||
sendEmail(user);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
// TODO - support more verification types
|
// TODO - support more verification types
|
||||||
function sendEmail(user) {
|
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 ' +
|
verifyOptions.text = verifyOptions.text.replace(/\{href\}/g, verifyOptions.verifyHref);
|
||||||
'this link in a web browser:\n\t%s', options.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 || {};
|
// Remove verifyOptions.template to prevent rejection by certain
|
||||||
|
|
||||||
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
|
|
||||||
// nodemailer transport plugins.
|
// nodemailer transport plugins.
|
||||||
delete options.template;
|
delete verifyOptions.template;
|
||||||
|
|
||||||
Email.send(options, function(err, email) {
|
// argument "options" is passed depending on Email.send function requirements
|
||||||
if (err) {
|
var Email = verifyOptions.mailer;
|
||||||
fn(err);
|
if (Email.send.length == 3) {
|
||||||
} else {
|
Email.send(verifyOptions, options, handleAfterSend);
|
||||||
fn(null, {email: email, token: user.verificationToken, uid: user[pkName]});
|
} 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) {
|
function assertVerifyOptions(verifyOptions) {
|
||||||
var template = loopback.template(options.template);
|
assert(verifyOptions.type, 'You must supply a verification type (verifyOptions.type)');
|
||||||
var body = template(options);
|
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);
|
cb(null, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -603,9 +630,10 @@ module.exports = function(User) {
|
||||||
* function will be called with the `user` object as it's context (`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 {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) {
|
crypto.randomBytes(64, function(err, buf) {
|
||||||
cb(err, buf && buf.toString('hex'));
|
cb(err, buf && buf.toString('hex'));
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,10 +8,12 @@ var assert = require('assert');
|
||||||
var expect = require('./helpers/expect');
|
var expect = require('./helpers/expect');
|
||||||
var request = require('supertest');
|
var request = require('supertest');
|
||||||
var loopback = require('../');
|
var loopback = require('../');
|
||||||
var User, AccessToken;
|
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var url = require('url');
|
var url = require('url');
|
||||||
var extend = require('util')._extend;
|
var extend = require('util')._extend;
|
||||||
|
var Promise = require('bluebird');
|
||||||
|
|
||||||
|
var User, AccessToken;
|
||||||
|
|
||||||
describe('User', function() {
|
describe('User', function() {
|
||||||
this.timeout(10000);
|
this.timeout(10000);
|
||||||
|
@ -30,14 +32,14 @@ describe('User', function() {
|
||||||
var validMixedCaseEmailCredentials = {email: 'Foo@bar.com', password: 'bar'};
|
var validMixedCaseEmailCredentials = {email: 'Foo@bar.com', password: 'bar'};
|
||||||
var invalidCredentials = {email: 'foo1@bar.com', password: 'invalid'};
|
var invalidCredentials = {email: 'foo1@bar.com', password: 'invalid'};
|
||||||
var incompleteCredentials = {password: 'bar1'};
|
var incompleteCredentials = {password: 'bar1'};
|
||||||
var validCredentialsUser, validCredentialsEmailVerifiedUser;
|
var validCredentialsUser, validCredentialsEmailVerifiedUser, user;
|
||||||
|
|
||||||
// Create a local app variable to prevent clashes with the global
|
// Create a local app variable to prevent clashes with the global
|
||||||
// variable shared by all tests. While this should not be necessary if
|
// 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 :(
|
// the tests were written correctly, it turns out that's not the case :(
|
||||||
var app = null;
|
var app = null;
|
||||||
|
|
||||||
beforeEach(function setupAppAndModels(done) {
|
beforeEach(function setupAppAndModels() {
|
||||||
// override the global app object provided by test/support.js
|
// override the global app object provided by test/support.js
|
||||||
// and create a local one that does not share state with other tests
|
// and create a local one that does not share state with other tests
|
||||||
app = loopback({localRegistry: true, loadBuiltinModels: true});
|
app = loopback({localRegistry: true, loadBuiltinModels: true});
|
||||||
|
@ -90,14 +92,13 @@ describe('User', function() {
|
||||||
app.use(loopback.token({model: AccessToken}));
|
app.use(loopback.token({model: AccessToken}));
|
||||||
app.use(loopback.rest());
|
app.use(loopback.rest());
|
||||||
|
|
||||||
User.create(validCredentials, function(err, user) {
|
// create 2 users: with and without verified email
|
||||||
if (err) return done(err);
|
return Promise.map(
|
||||||
validCredentialsUser = user;
|
[validCredentials, validCredentialsEmailVerified],
|
||||||
User.create(validCredentialsEmailVerified, function(err, user) {
|
credentials => User.create(credentials)
|
||||||
if (err) return done(err);
|
).then(users => {
|
||||||
validCredentialsEmailVerifiedUser = user;
|
validCredentialsUser = user = users[0];
|
||||||
done();
|
validCredentialsEmailVerifiedUser = users[1];
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1405,21 +1406,23 @@ describe('User', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Verification', 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) {
|
it('Verify a user\'s email address', function(done) {
|
||||||
User.afterRemote('create', function(ctx, user, next) {
|
User.afterRemote('create', function(ctx, user, next) {
|
||||||
assert(user, 'afterRemote should include result');
|
assert(user, 'afterRemote should include result');
|
||||||
|
|
||||||
var options = {
|
user.verify(verifyOptions, function(err, result) {
|
||||||
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) {
|
|
||||||
assert(result.email);
|
assert(result.email);
|
||||||
assert(result.email.response);
|
assert(result.email.response);
|
||||||
assert(result.token);
|
assert(result.token);
|
||||||
|
@ -1445,16 +1448,7 @@ describe('User', function() {
|
||||||
User.afterRemote('create', function(ctx, user, next) {
|
User.afterRemote('create', function(ctx, user, next) {
|
||||||
assert(user, 'afterRemote should include result');
|
assert(user, 'afterRemote should include result');
|
||||||
|
|
||||||
var options = {
|
user.verify(verifyOptions)
|
||||||
type: 'email',
|
|
||||||
to: user.email,
|
|
||||||
from: 'noreply@myapp.org',
|
|
||||||
redirect: '/',
|
|
||||||
protocol: ctx.req.protocol,
|
|
||||||
host: ctx.req.get('host'),
|
|
||||||
};
|
|
||||||
|
|
||||||
user.verify(options)
|
|
||||||
.then(function(result) {
|
.then(function(result) {
|
||||||
assert(result.email);
|
assert(result.email);
|
||||||
assert(result.email.response);
|
assert(result.email.response);
|
||||||
|
@ -1484,17 +1478,9 @@ describe('User', function() {
|
||||||
User.afterRemote('create', function(ctx, user, next) {
|
User.afterRemote('create', function(ctx, user, next) {
|
||||||
assert(user, 'afterRemote should include result');
|
assert(user, 'afterRemote should include result');
|
||||||
|
|
||||||
var options = {
|
verifyOptions.headers = {'message-id': 'custom-header-value'};
|
||||||
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'},
|
|
||||||
};
|
|
||||||
|
|
||||||
user.verify(options, function(err, result) {
|
user.verify(verifyOptions, function(err, result) {
|
||||||
assert(result.email);
|
assert(result.email);
|
||||||
assert.equal(result.email.messageId, 'custom-header-value');
|
assert.equal(result.email.messageId, 'custom-header-value');
|
||||||
|
|
||||||
|
@ -1516,19 +1502,11 @@ describe('User', function() {
|
||||||
User.afterRemote('create', function(ctx, user, next) {
|
User.afterRemote('create', function(ctx, user, next) {
|
||||||
assert(user, 'afterRemote should include result');
|
assert(user, 'afterRemote should include result');
|
||||||
|
|
||||||
var options = {
|
verifyOptions.templateFn = function(verifyOptions, cb) {
|
||||||
type: 'email',
|
cb(null, 'custom template - verify url: ' + verifyOptions.verifyHref);
|
||||||
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);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
user.verify(options, function(err, result) {
|
user.verify(verifyOptions, function(err, result) {
|
||||||
assert(result.email);
|
assert(result.email);
|
||||||
assert(result.email.response);
|
assert(result.email.response);
|
||||||
assert(result.token);
|
assert(result.token);
|
||||||
|
@ -1558,17 +1536,9 @@ describe('User', function() {
|
||||||
User.afterRemote('create', function(ctx, user, next) {
|
User.afterRemote('create', function(ctx, user, next) {
|
||||||
assert(user, 'afterRemote should include result');
|
assert(user, 'afterRemote should include result');
|
||||||
|
|
||||||
var options = {
|
verifyOptions.templateFn = function(verifyOptions, cb) {
|
||||||
type: 'email',
|
actualVerifyHref = verifyOptions.verifyHref;
|
||||||
to: user.email,
|
cb(null, 'dummy body');
|
||||||
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');
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// replace the string id with an object
|
// replace the string id with an object
|
||||||
|
@ -1579,7 +1549,7 @@ describe('User', function() {
|
||||||
});
|
});
|
||||||
user.pk = {toString: function() { return idString; }};
|
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).to.exist().and.be.an('object');
|
||||||
expect(result.uid.toString()).to.equal(idString);
|
expect(result.uid.toString()).to.equal(idString);
|
||||||
const parsed = url.parse(actualVerifyHref, true);
|
const parsed = url.parse(actualVerifyHref, true);
|
||||||
|
@ -1602,26 +1572,18 @@ describe('User', function() {
|
||||||
User.afterRemote('create', function(ctx, user, next) {
|
User.afterRemote('create', function(ctx, user, next) {
|
||||||
assert(user, 'afterRemote should include result');
|
assert(user, 'afterRemote should include result');
|
||||||
|
|
||||||
var options = {
|
verifyOptions.generateVerificationToken = function(user, cb) {
|
||||||
type: 'email',
|
assert(user);
|
||||||
to: user.email,
|
assert.equal(user.email, 'bar@bat.com');
|
||||||
from: 'noreply@myapp.org',
|
assert(cb);
|
||||||
redirect: '/',
|
assert.equal(typeof cb, 'function');
|
||||||
protocol: ctx.req.protocol,
|
// let's ensure async execution works on this one
|
||||||
host: ctx.req.get('host'),
|
process.nextTick(function() {
|
||||||
generateVerificationToken: function(user, cb) {
|
cb(null, 'token-123456');
|
||||||
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);
|
||||||
assert(result.email.response);
|
assert(result.email.response);
|
||||||
assert(result.token);
|
assert(result.token);
|
||||||
|
@ -1647,22 +1609,14 @@ describe('User', function() {
|
||||||
User.afterRemote('create', function(ctx, user, next) {
|
User.afterRemote('create', function(ctx, user, next) {
|
||||||
assert(user, 'afterRemote should include result');
|
assert(user, 'afterRemote should include result');
|
||||||
|
|
||||||
var options = {
|
verifyOptions.generateVerificationToken = function(user, cb) {
|
||||||
type: 'email',
|
// let's ensure async execution works on this one
|
||||||
to: user.email,
|
process.nextTick(function() {
|
||||||
from: 'noreply@myapp.org',
|
cb(new Error('Fake error'));
|
||||||
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) {
|
user.verify(verifyOptions, function(err, result) {
|
||||||
assert(err);
|
assert(err);
|
||||||
assert.equal(err.message, 'Fake error');
|
assert.equal(err.message, 'Fake error');
|
||||||
assert.equal(result, undefined);
|
assert.equal(result, undefined);
|
||||||
|
@ -1686,17 +1640,12 @@ describe('User', function() {
|
||||||
User.afterRemote('create', function(ctx, user, next) {
|
User.afterRemote('create', function(ctx, user, next) {
|
||||||
assert(user, 'afterRemote should include result');
|
assert(user, 'afterRemote should include result');
|
||||||
|
|
||||||
var options = {
|
Object.assign(verifyOptions, {
|
||||||
type: 'email',
|
|
||||||
to: user.email,
|
|
||||||
from: 'noreply@myapp.org',
|
|
||||||
redirect: '/',
|
|
||||||
protocol: 'http',
|
|
||||||
host: 'myapp.org',
|
host: 'myapp.org',
|
||||||
port: 3000,
|
port: 3000,
|
||||||
};
|
});
|
||||||
|
|
||||||
user.verify(options, function(err, result) {
|
user.verify(verifyOptions, function(err, result) {
|
||||||
var msg = result.email.response.toString('utf-8');
|
var msg = result.email.response.toString('utf-8');
|
||||||
assert(~msg.indexOf('http://myapp.org:3000/'));
|
assert(~msg.indexOf('http://myapp.org:3000/'));
|
||||||
|
|
||||||
|
@ -1718,17 +1667,12 @@ describe('User', function() {
|
||||||
User.afterRemote('create', function(ctx, user, next) {
|
User.afterRemote('create', function(ctx, user, next) {
|
||||||
assert(user, 'afterRemote should include result');
|
assert(user, 'afterRemote should include result');
|
||||||
|
|
||||||
var options = {
|
Object.assign(verifyOptions, {
|
||||||
type: 'email',
|
|
||||||
to: user.email,
|
|
||||||
from: 'noreply@myapp.org',
|
|
||||||
redirect: '/',
|
|
||||||
protocol: 'http',
|
|
||||||
host: 'myapp.org',
|
host: 'myapp.org',
|
||||||
port: 80,
|
port: 80,
|
||||||
};
|
});
|
||||||
|
|
||||||
user.verify(options, function(err, result) {
|
user.verify(verifyOptions, function(err, result) {
|
||||||
var msg = result.email.response.toString('utf-8');
|
var msg = result.email.response.toString('utf-8');
|
||||||
assert(~msg.indexOf('http://myapp.org/'));
|
assert(~msg.indexOf('http://myapp.org/'));
|
||||||
|
|
||||||
|
@ -1750,17 +1694,13 @@ describe('User', function() {
|
||||||
User.afterRemote('create', function(ctx, user, next) {
|
User.afterRemote('create', function(ctx, user, next) {
|
||||||
assert(user, 'afterRemote should include result');
|
assert(user, 'afterRemote should include result');
|
||||||
|
|
||||||
var options = {
|
Object.assign(verifyOptions, {
|
||||||
type: 'email',
|
|
||||||
to: user.email,
|
|
||||||
from: 'noreply@myapp.org',
|
|
||||||
redirect: '/',
|
|
||||||
protocol: 'https',
|
|
||||||
host: 'myapp.org',
|
host: 'myapp.org',
|
||||||
port: 3000,
|
port: 3000,
|
||||||
};
|
protocol: 'https',
|
||||||
|
});
|
||||||
|
|
||||||
user.verify(options, function(err, result) {
|
user.verify(verifyOptions, function(err, result) {
|
||||||
var msg = result.email.response.toString('utf-8');
|
var msg = result.email.response.toString('utf-8');
|
||||||
assert(~msg.indexOf('https://myapp.org:3000/'));
|
assert(~msg.indexOf('https://myapp.org:3000/'));
|
||||||
|
|
||||||
|
@ -1782,17 +1722,13 @@ describe('User', function() {
|
||||||
User.afterRemote('create', function(ctx, user, next) {
|
User.afterRemote('create', function(ctx, user, next) {
|
||||||
assert(user, 'afterRemote should include result');
|
assert(user, 'afterRemote should include result');
|
||||||
|
|
||||||
var options = {
|
Object.assign(verifyOptions, {
|
||||||
type: 'email',
|
|
||||||
to: user.email,
|
|
||||||
from: 'noreply@myapp.org',
|
|
||||||
redirect: '/',
|
|
||||||
protocol: 'https',
|
|
||||||
host: 'myapp.org',
|
host: 'myapp.org',
|
||||||
|
protocol: 'https',
|
||||||
port: 443,
|
port: 443,
|
||||||
};
|
});
|
||||||
|
|
||||||
user.verify(options, function(err, result) {
|
user.verify(verifyOptions, function(err, result) {
|
||||||
var msg = result.email.response.toString('utf-8');
|
var msg = result.email.response.toString('utf-8');
|
||||||
assert(~msg.indexOf('https://myapp.org/'));
|
assert(~msg.indexOf('https://myapp.org/'));
|
||||||
|
|
||||||
|
@ -1828,17 +1764,13 @@ describe('User', function() {
|
||||||
User.afterRemote('create', function(ctx, user, next) {
|
User.afterRemote('create', function(ctx, user, next) {
|
||||||
assert(user, 'afterRemote should include result');
|
assert(user, 'afterRemote should include result');
|
||||||
|
|
||||||
var options = {
|
Object.assign(verifyOptions, {
|
||||||
type: 'email',
|
|
||||||
to: user.email,
|
|
||||||
from: 'noreply@myapp.org',
|
|
||||||
redirect: '/',
|
|
||||||
host: 'myapp.org',
|
host: 'myapp.org',
|
||||||
port: 3000,
|
port: 3000,
|
||||||
restApiRoot: '/',
|
restApiRoot: '/',
|
||||||
};
|
});
|
||||||
|
|
||||||
user.verify(options, function(err, result) {
|
user.verify(verifyOptions, function(err, result) {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
emailBody = result.email.response.toString('utf-8');
|
emailBody = result.email.response.toString('utf-8');
|
||||||
next();
|
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 = {
|
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'})
|
return user.verify(verifyOptions)
|
||||||
.then(function(user) {
|
|
||||||
return user.verify({
|
|
||||||
type: 'email',
|
|
||||||
from: 'noreply@example.com',
|
|
||||||
mailer: MailerMock,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then(function(result) {
|
.then(function(result) {
|
||||||
expect(result.email).to.not.have.property('template');
|
expect(result.email).to.not.have.property('template');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('allows hash fragment in redirectUrl', function() {
|
it('allows hash fragment in redirectUrl', function() {
|
||||||
return User.create({email: 'test@example.com', password: 'pass'})
|
let actualVerifyHref;
|
||||||
.then(user => {
|
|
||||||
let actualVerifyHref;
|
Object.assign(verifyOptions, {
|
||||||
return user.verify({
|
redirect: '#/some-path?a=1&b=2',
|
||||||
type: 'email',
|
templateFn: (verifyOptions, cb) => {
|
||||||
to: user.email,
|
actualVerifyHref = verifyOptions.verifyHref;
|
||||||
from: 'noreply@myapp.org',
|
cb(null, 'dummy body');
|
||||||
redirect: '#/some-path?a=1&b=2',
|
},
|
||||||
templateFn: (options, cb) => {
|
});
|
||||||
actualVerifyHref = options.verifyHref;
|
|
||||||
cb(null, 'dummy body');
|
return user.verify(verifyOptions)
|
||||||
},
|
.then(() => actualVerifyHref)
|
||||||
})
|
|
||||||
.then(() => actualVerifyHref);
|
|
||||||
})
|
|
||||||
.then(verifyHref => {
|
.then(verifyHref => {
|
||||||
var parsed = url.parse(verifyHref, true);
|
var parsed = url.parse(verifyHref, true);
|
||||||
expect(parsed.query.redirect, 'redirect query')
|
expect(parsed.query.redirect, 'redirect query')
|
||||||
|
@ -1899,36 +1822,107 @@ describe('User', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('verify that options.templateFn receives options.verificationToken', function() {
|
it('verifies that verifyOptions.templateFn receives verifyOptions.verificationToken',
|
||||||
return User.create({email: 'test1@example.com', password: 'pass'})
|
function() {
|
||||||
.then(user => {
|
let actualVerificationToken;
|
||||||
let actualVerificationToken;
|
|
||||||
return user.verify({
|
Object.assign(verifyOptions, {
|
||||||
type: 'email',
|
redirect: '#/some-path?a=1&b=2',
|
||||||
to: user.email,
|
templateFn: (verifyOptions, cb) => {
|
||||||
from: 'noreply@myapp.org',
|
actualVerificationToken = verifyOptions.verificationToken;
|
||||||
redirect: '#/some-path?a=1&b=2',
|
cb(null, 'dummy body');
|
||||||
templateFn: (options, cb) => {
|
},
|
||||||
actualVerificationToken = options.verificationToken;
|
});
|
||||||
cb(null, 'dummy body');
|
|
||||||
},
|
return user.verify(verifyOptions)
|
||||||
})
|
.then(() => actualVerificationToken)
|
||||||
.then(() => actualVerificationToken);
|
|
||||||
})
|
|
||||||
.then(token => {
|
.then(token => {
|
||||||
expect(token).to.exist();
|
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() {
|
describe('User.confirm(options, fn)', function() {
|
||||||
var options;
|
var verifyOptions;
|
||||||
|
|
||||||
function testConfirm(testFunc, done) {
|
function testConfirm(testFunc, done) {
|
||||||
User.afterRemote('create', function(ctx, user, next) {
|
User.afterRemote('create', function(ctx, user, next) {
|
||||||
assert(user, 'afterRemote should include result');
|
assert(user, 'afterRemote should include result');
|
||||||
|
|
||||||
options = {
|
verifyOptions = {
|
||||||
type: 'email',
|
type: 'email',
|
||||||
to: user.email,
|
to: user.email,
|
||||||
from: 'noreply@myapp.org',
|
from: 'noreply@myapp.org',
|
||||||
|
@ -1937,7 +1931,7 @@ describe('User', function() {
|
||||||
host: ctx.req.get('host'),
|
host: ctx.req.get('host'),
|
||||||
};
|
};
|
||||||
|
|
||||||
user.verify(options, function(err, result) {
|
user.verify(verifyOptions, function(err, result) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
testFunc(result, done);
|
testFunc(result, done);
|
||||||
|
@ -1959,7 +1953,7 @@ describe('User', function() {
|
||||||
request(app)
|
request(app)
|
||||||
.get('/test-users/confirm?uid=' + (result.uid) +
|
.get('/test-users/confirm?uid=' + (result.uid) +
|
||||||
'&token=' + encodeURIComponent(result.token) +
|
'&token=' + encodeURIComponent(result.token) +
|
||||||
'&redirect=' + encodeURIComponent(options.redirect))
|
'&redirect=' + encodeURIComponent(verifyOptions.redirect))
|
||||||
.expect(302)
|
.expect(302)
|
||||||
.end(function(err, res) {
|
.end(function(err, res) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
@ -2011,7 +2005,7 @@ describe('User', function() {
|
||||||
request(app)
|
request(app)
|
||||||
.get('/test-users/confirm?uid=' + (result.uid + '_invalid') +
|
.get('/test-users/confirm?uid=' + (result.uid + '_invalid') +
|
||||||
'&token=' + encodeURIComponent(result.token) +
|
'&token=' + encodeURIComponent(result.token) +
|
||||||
'&redirect=' + encodeURIComponent(options.redirect))
|
'&redirect=' + encodeURIComponent(verifyOptions.redirect))
|
||||||
.expect(404)
|
.expect(404)
|
||||||
.end(function(err, res) {
|
.end(function(err, res) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
@ -2030,7 +2024,7 @@ describe('User', function() {
|
||||||
request(app)
|
request(app)
|
||||||
.get('/test-users/confirm?uid=' + result.uid +
|
.get('/test-users/confirm?uid=' + result.uid +
|
||||||
'&token=' + encodeURIComponent(result.token) + '_invalid' +
|
'&token=' + encodeURIComponent(result.token) + '_invalid' +
|
||||||
'&redirect=' + encodeURIComponent(options.redirect))
|
'&redirect=' + encodeURIComponent(verifyOptions.redirect))
|
||||||
.expect(400)
|
.expect(400)
|
||||||
.end(function(err, res) {
|
.end(function(err, res) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
Loading…
Reference in New Issue