activator
Version:
simple user activation and password reset for nodejs
1,248 lines (1,239 loc) • 67.3 kB
JavaScript
/*jslint debug:true */
/*jshint unused:vars */
/*global describe, before, beforeEach, it */
"use strict";
var request = require('supertest'), should = require('should'), express = require('express'), bodyParser = require('body-parser'),
app = express(), _ = require('lodash'), async = require('async'), smtp = require('smtp-tester'),
r = request(app), mail, fs = require('fs'),
activator = require('../lib/activator'),
baseTemplatesPath = __dirname+'/resources',
htmlTemplatesPath = baseTemplatesPath+'/html',
localeTemplatesPath = baseTemplatesPath+'/locale',
templates = activator.templates.file(baseTemplatesPath),
htmlTemplates = activator.templates.file(htmlTemplatesPath),
localeTemplates = activator.templates.file(localeTemplatesPath),
mailer = require('nodemailer'), jwt = require('jsonwebtoken'), SIGNKEY = "1234567890abcdefghijklmn",
USERS = {
"1": {id:"1",childObject:{id:"1"},email:"me@you.com",password:"1234",activated:false}
}, lang,
users,
quote = function (regex) {
/*jslint regexp:true */
var ret = regex.replace(/([()[{*+.$^\\/\\|?])/g, '\\$1');
/*jslint regexp:false */
return(ret);
},
bodyMatcher = function (body,matcher) {
/*jslint regexp:true */
var ret = body.replace(/[\r\n]+/g,'').match(new RegExp(quote(matcher.replace(/[\r\n]+/g,'')).replace(/<%=[^%]+%>/g,'.*')));
/*jslint regexp:false */
return(ret);
},
changeResetTime = function (token,diff) {
var original = jwt.decode(token), code;
original.iat = original.iat - 100*60;
code = jwt.sign(original,SIGNKEY,{algorithm:"HS256"});
return code;
},
userModel = {
_find: function (login,cb) {
var found = null;
if (!login) {
cb("nologin");
} else if (users[login]) {
cb(null,_.cloneDeep(users[login]));
} else {
_.each(users,function (val) {
if (val && val.email === login) {
found = val;
return(false);
}
});
cb(null,_.cloneDeep(found));
}
},
find: function() {
this._find.apply(this,arguments);
},
activate: function (id,cb) {
if (id && users[id]) {
users[id].activated = true;
cb(null);
} else {
cb(404);
}
},
setPassword: function (id,password,cb) {
if (id && users[id]) {
users[id].password = password;
cb(null,null);
} else {
cb(404);
}
},
},
reset = function () {
users = _.cloneDeep(USERS);
if (mail && mail.removeAll) {
mail.removeAll();
}
},
userModelEmail = _.extend({},userModel,{find: function (login,cb) {
this._find(login,function (err,res) {
if (res && res.email) {
res.funny = res.email;
res.childObject = {
funny: res.email
};
delete res.email;
}
cb(err,res);
});
}
}),
MAILPORT = 30111,
url = "smtp://localhost:"+MAILPORT+"/activator.net",
maileropts = { host: "localhost", port:MAILPORT, name: "activator.net", secureConnection: false },
from = "test@activator.net",
createUser = function (req,res,next) {
// id if the next unique ID
let id = (Math.max.apply(null,_.keys(users).map(function(i){return parseInt(i,10);}))+1).toString();
users[id] = {id:id,childObject:{id:id},email:id+"@foo.com",password:"5678"};
req.activator = {id:id,body:id};
next();
},
splitTemplate = function (path) {
/*jslint stupid:true */
var content = fs.readFileSync(path,'utf8');
content = content.replace(/\r\n/g,'\n');
/*jslint stupid:false */
content = content.match(/^([^\n]*)\n[^\n]*\n((.|\n)*)/m);
return(content);
},
genHandler = function(email,path,data,cb) {
return function(rcpt,msgid,content) {
var url, ret, re = new RegExp('http:\\/\\/\\S*'+path.replace(/\//g,'\\/')+'\\?code=([^\\s\\&]+)\\&email=(\\S+)\\&user=([^\\s\\&]+)'),
subject = data.subject;
rcpt.should.eql(email);
// check for the correct Subject in the email
should.exist(content.data);
content.headers.subject.should.eql(subject);
// do we have actual content to test? if so, we should ignore templates, because we do not have the request stuff
if (data.body) {
should.exist(content.body);
should.exist(bodyMatcher(content.body,data.body));
url = content.body.match(re);
should.exist(url);
// check that code and email match what is in database
url.length.should.eql(4);
ret = _.zipObject(["path","code","email","user"],url);
ret.email.should.eql(email);
}
if (data.html) {
should.exist(content.html);
should.exist(bodyMatcher(content.html,data.html));
url = content.html.match(re);
should.exist(url);
// check that code and email match what is in database
url.length.should.eql(4);
ret = _.zipObject(["path","code","email","user"],url);
ret.email.should.eql(email);
}
if (!ret) {
url = (content.body||content.html).match(re);
should.exist(url);
// check that code and email match what is in database
url.length.should.eql(4);
ret = _.zipObject(["path","code","email","user"],url);
ret.email.should.eql(email);
}
ret.content = content;
cb(null,ret);
};
},
aHandler = function (email,data,cb) {
if (!cb) {
cb = data;
data = {};
}
// set up the default subject
data.subject = data.subject || "Activate Email";
return genHandler(email,"/activate/my/account",data,cb);
},
rHandler = function(email,data,cb) {
if (!cb) {
cb = data;
data = {};
}
// set up the default subject
data.subject = data.subject || "Password Reset Email";
return genHandler(email,"/reset/my/password",data,cb);
},
cHandler = function(email,data,cb) {
if (!cb) {
cb = data;
data = {};
}
// set up the default subject
data.subject = data.subject || "Password Reset Complete Email";
data.password = data.password || "abcdefgh";
return function (rcpt, msgid, content) {
var passwordRe = new RegExp('Password: ('+data.password+')'), match;
rcpt.should.eql(email);
// check for the correct Subject in the email
should.exist(content.data);
content.headers.subject.should.eql(data.subject);
match = content.body.match(passwordRe);
match.length.should.eql(2);
match[1].should.eql(data.password);
if (data.body) {
should.exist(content.body);
should.exist(bodyMatcher(content.body,data.body));
}
if (data.html) {
should.exist(content.html);
should.exist(bodyMatcher(content.html,data.html));
}
cb(null,null);
};
},
createActivateHandler = function (req,res,next) {
// the header is not normally set, so we know we incurred the handler
res.set("activator","createActivateHandler");
res.status(req.activator.code).send(req.activator.message);
},
completeActivateHandler = function (req,res,next) {
// the header is not normally set, so we know we incurred the handler
res.set("activator","completeActivateHandler");
res.status(req.activator.code).send(req.activator.message);
},
createResetHandler = function (req,res,next) {
var msg = req.activator.message;
// the header is not normally set, so we know we incurred the handler
res.set("activator","createResetHandler");
if (msg === null || msg === undefined || typeof(msg) === "number") {
res.sendStatus(req.activator.code);
} else {
res.status(req.activator.code).send(req.activator.message);
}
},
completeResetHandler = function (req,res,next) {
// the header is not normally set, so we know we incurred the handler
res.set("activator","completeResetHandler");
res.status(req.activator.code).send(req.activator.message);
},
setLang = function (req,res,next) {
if (lang) {
req.lang = lang;
}
next();
},
allTests;
before(function(){
debugger;
});
before(function(){
reset();
});
allTests = function () {
beforeEach(reset);
describe('activate', function(){
it('should send 500 for user property not added', function(done){
r.post('/usersbad').expect(500,done);
});
describe('auth header', function(){
it('should fail for known user but bad code', function(done){
var email, handler;
async.waterfall([
function (cb) {r.post('/users').expect(201,"2",cb);},
function (res,cb) {
email = users["2"].email;
handler = aHandler(email,cb);
mail.bind(email,handler);
},
function (res,cb) {
mail.unbind(email,handler);
r.put('/users/'+res.user+'/activate').set({'authorization':"Bearer asasqsqsqs"}).expect(400,'invalidcode',cb);
}
],done);
});
it('should fail for known user but bad code with handler', function(done){
var email, handler;
async.waterfall([
function (cb) {r.post('/usersnext').expect('activator','createActivateHandler').expect(201,cb);},
function (res,cb) {
email = users["2"].email;
handler = aHandler(email,cb);
mail.bind(email,handler);
},
function (res,cb) {
mail.unbind(email,handler);
r.put('/usersnext/'+res.user+'/activate').set({'authorization':"Bearer asasqsqsqs"}).expect('activator','completeActivateHandler').expect(400,cb);
}
],done);
});
it('should fail for another user', function(done){
var email, handler;
async.waterfall([
function (cb) {r.post('/users').expect(201,cb);},
function (res,cb) {r.post('/users').expect(201,cb);},
function (res,cb) {
res.text.should.equal("3");
email = users["3"].email;
handler = aHandler(email,cb);
mail.bind(email,handler);
},
function (res,cb) {
mail.unbind(email,handler);
r.put('/users/'+"2"+'/activate').set({"authorization":"Bearer "+res.code}).expect(400,'invalidcode',cb);
}
],done);
});
it('should fail for another user with handler', function(done){
var email, handler;
async.waterfall([
function (cb) {r.post('/usersnext').expect('activator','createActivateHandler').expect(201,cb);},
function (res,cb) {r.post('/usersnext').expect('activator','createActivateHandler').expect(201,cb);},
function (res,cb) {
res.text.should.equal("3");
email = users["3"].email;
handler = aHandler(email,cb);
mail.bind(email,handler);
},
function (res,cb) {
mail.unbind(email,handler);
// check there is no attachment
should(res.content.attachments).be.empty();
r.put('/usersnext/'+"2"+'/activate').set({"authorization":"Bearer "+res.code}).expect(400,'invalidcode',cb);
}
],done);
});
it('should succeed for known user', function(done){
var email, handler;
async.waterfall([
function (cb) {r.post('/users').expect(201,cb);},
function (res,cb) {
res.text.should.equal("2");
email = users["2"].email;
handler = aHandler(email,cb);
mail.bind(email,handler);
},
function (res,cb) {
mail.unbind(email,handler);
r.put('/users/'+res.user+'/activate').set({"authorization":"Bearer "+res.code}).expect(200,cb);
}
],done);
});
it('should succeed for known user with handler', function(done){
var email, handler;
async.waterfall([
function (cb) {r.post('/usersnext').expect('activator','createActivateHandler').expect(201,cb);},
function (res,cb) {
res.text.should.equal("2");
email = users["2"].email;
handler = aHandler(email,cb);
mail.bind(email,handler);
},
function (res,cb) {
mail.unbind(email,handler);
// check there is no attachment
should(res.content.attachments).be.empty();
r.put('/usersnext/'+res.user+'/activate').set({"authorization":"Bearer "+res.code}).expect('activator','completeActivateHandler').expect(200,cb);
}
],done);
});
it('should succeed for query authentication over basic header', function(done){
var email, handler;
async.waterfall([
function (cb) {r.post('/usersnext').expect('activator','createActivateHandler').expect(201,cb);},
function (res,cb) {
res.text.should.equal("2");
email = users["2"].email;
handler = aHandler(email,cb);
mail.bind(email,handler);
},
function (res,cb) {
mail.unbind(email,handler);
// check there is no attachment
should(res.content.attachments).be.empty();
r.put('/usersnext/'+res.user+'/activate').set({"authorization":"Basic foo"}).query({authorization:res.code}).expect('activator','completeActivateHandler').expect(200,cb);
}
],done);
});
});
describe('auth query', function(){
it('should fail for known user but bad code', function(done){
var email, handler;
async.waterfall([
function (cb) {r.post('/users').expect(201,"2",cb);},
function (res,cb) {
email = users["2"].email;
handler = aHandler(email,cb);
mail.bind(email,handler);
},
function (res,cb) {
mail.unbind(email,handler);
r.put('/users/'+res.user+'/activate').type("json").query({authorization:"asasqsqsqs"}).expect(400,'invalidcode',cb);
}
],done);
});
it('should fail for known user but bad code with handler', function(done){
var email, handler;
async.waterfall([
function (cb) {r.post('/usersnext').expect('activator','createActivateHandler').expect(201,cb);},
function (res,cb) {
email = users["2"].email;
handler = aHandler(email,cb);
mail.bind(email,handler);
},
function (res,cb) {
mail.unbind(email,handler);
r.put('/usersnext/'+res.user+'/activate').type("json").query({authorization:"asasqsqsqs"}).expect('activator','completeActivateHandler').expect(400,cb);
}
],done);
});
it('should fail for another user', function(done){
var email, handler;
async.waterfall([
function (cb) {r.post('/users').expect(201,cb);},
function (res,cb) {r.post('/users').expect(201,cb);},
function (res,cb) {
res.text.should.equal("3");
email = users["3"].email;
handler = aHandler(email,cb);
mail.bind(email,handler);
},
function (res,cb) {
mail.unbind(email,handler);
r.put('/users/'+"2"+'/activate').type("json").query({authorization:res.code}).expect(400,'invalidcode',cb);
}
],done);
});
it('should fail for another user with handler', function(done){
var email, handler;
async.waterfall([
function (cb) {r.post('/usersnext').expect('activator','createActivateHandler').expect(201,cb);},
function (res,cb) {r.post('/usersnext').expect('activator','createActivateHandler').expect(201,cb);},
function (res,cb) {
res.text.should.equal("3");
email = users["3"].email;
handler = aHandler(email,cb);
mail.bind(email,handler);
},
function (res,cb) {
mail.unbind(email,handler);
// check there is no attachment
should(res.content.attachments).be.empty();
r.put('/usersnext/'+"2"+'/activate').type("json").query({authorization:res.code}).expect(400,'invalidcode',cb);
}
],done);
});
it('should succeed for known user', function(done){
var email, handler;
async.waterfall([
function (cb) {r.post('/users').expect(201,cb);},
function (res,cb) {
res.text.should.equal("2");
email = users["2"].email;
handler = aHandler(email,cb);
mail.bind(email,handler);
},
function (res,cb) {
mail.unbind(email,handler);
r.put('/users/'+res.user+'/activate').type("json").query({authorization:res.code}).expect(200,cb);
}
],done);
});
it('should succeed for known user with handler', function(done){
var email, handler;
async.waterfall([
function (cb) {r.post('/usersnext').expect('activator','createActivateHandler').expect(201,cb);},
function (res,cb) {
res.text.should.equal("2");
email = users["2"].email;
handler = aHandler(email,cb);
mail.bind(email,handler);
},
function (res,cb) {
mail.unbind(email,handler);
// check there is no attachment
should(res.content.attachments).be.empty();
r.put('/usersnext/'+res.user+'/activate').type("json").query({authorization:res.code}).expect('activator','completeActivateHandler').expect(200,cb);
}
],done);
});
});
describe('auth body', function(){
it('should fail for known user but bad code', function(done){
var email, handler;
async.waterfall([
function (cb) {r.post('/users').expect(201,"2",cb);},
function (res,cb) {
email = users["2"].email;
handler = aHandler(email,cb);
mail.bind(email,handler);
},
function (res,cb) {
mail.unbind(email,handler);
r.put('/users/'+res.user+'/activate').type("json").send({authorization:"asasqsqsqs"}).expect(400,'invalidcode',cb);
}
],done);
});
it('should fail for known user but bad code with handler', function(done){
var email, handler;
async.waterfall([
function (cb) {r.post('/usersnext').expect('activator','createActivateHandler').expect(201,cb);},
function (res,cb) {
email = users["2"].email;
handler = aHandler(email,cb);
mail.bind(email,handler);
},
function (res,cb) {
mail.unbind(email,handler);
r.put('/usersnext/'+res.user+'/activate').type("json").send({authorization:"asasqsqsqs"}).expect('activator','completeActivateHandler').expect(400,cb);
}
],done);
});
it('should fail for another user', function(done){
var email, handler;
async.waterfall([
function (cb) {r.post('/users').expect(201,cb);},
function (res,cb) {r.post('/users').expect(201,cb);},
function (res,cb) {
res.text.should.equal("3");
email = users["3"].email;
handler = aHandler(email,cb);
mail.bind(email,handler);
},
function (res,cb) {
mail.unbind(email,handler);
r.put('/users/'+"2"+'/activate').type("json").send({authorization:res.code}).expect(400,'invalidcode',cb);
}
],done);
});
it('should fail for another user with handler', function(done){
var email, handler;
async.waterfall([
function (cb) {r.post('/usersnext').expect('activator','createActivateHandler').expect(201,cb);},
function (res,cb) {r.post('/usersnext').expect('activator','createActivateHandler').expect(201,cb);},
function (res,cb) {
res.text.should.equal("3");
email = users["3"].email;
handler = aHandler(email,cb);
mail.bind(email,handler);
},
function (res,cb) {
mail.unbind(email,handler);
// check there is no attachment
should(res.content.attachments).be.empty();
r.put('/usersnext/'+"2"+'/activate').type("json").send({authorization:res.code}).expect(400,'invalidcode',cb);
}
],done);
});
it('should succeed for known user', function(done){
var email, handler;
async.waterfall([
function (cb) {r.post('/users').expect(201,cb);},
function (res,cb) {
res.text.should.equal("2");
email = users["2"].email;
handler = aHandler(email,cb);
mail.bind(email,handler);
},
function (res,cb) {
mail.unbind(email,handler);
r.put('/users/'+res.user+'/activate').type("json").send({authorization:res.code}).expect(200,cb);
}
],done);
});
it('should succeed for known user with handler', function(done){
var email, handler;
async.waterfall([
function (cb) {r.post('/usersnext').expect('activator','createActivateHandler').expect(201,cb);},
function (res,cb) {
res.text.should.equal("2");
email = users["2"].email;
handler = aHandler(email,cb);
mail.bind(email,handler);
},
function (res,cb) {
mail.unbind(email,handler);
// check there is no attachment
should(res.content.attachments).be.empty();
r.put('/usersnext/'+res.user+'/activate').type("json").send({authorization:res.code}).expect('activator','completeActivateHandler').expect(200,cb);
}
],done);
});
});
});
describe('password reset', function(){
it('should send 400 for no email or ID passed', function(done){
r.post("/passwordreset").expect(400,done);
});
it('should send 400 for no email or ID passed with handler', function(done){
r.post("/passwordresetnext").expect('activator','createResetHandler').expect(400,done);
});
it('should send 404 for unknown email or ID', function(done){
r.post("/passwordreset").type('json').send({user:"john@john.com"}).expect(404,done);
});
it('should send 404 for unknown email or ID with handler', function(done){
r.post("/passwordresetnext").type('json').send({user:"john@john.com"}).expect('activator','createResetHandler').expect(404,done);
});
describe('auth header', function(){
it('should fail for known email but bad code', function(done){
var email = users["1"].email, handler;
async.waterfall([
function (cb) {
r.post('/passwordreset').type('json').send({user:email}).expect(201,cb);},
function (res,cb) {
handler = rHandler(email,cb);
mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
r.put('/passwordreset/'+res.user).set({"Authorization":"Bearer asasqsqsqs"}).type("json").send({password:"asasa"}).expect(400,cb);
}
],done);
});
it('should fail for known email but bad code with handler', function(done){
var email = users["1"].email, handler;
async.waterfall([
function (cb) {
r.post('/passwordresetnext').type('json').send({user:email}).expect('activator','createResetHandler').expect(201,cb);},
function (res,cb) {
handler = rHandler(email,cb);
mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
r.put('/passwordresetnext/'+res.user).set({"Authorization":"Bearer asasqsqsqs"}).type("json").send({password:"asasa"}).expect('activator','completeResetHandler').expect(400,cb);
}
],done);
});
it('should fail for known email with good code but missing new password', function(done){
var email = users["1"].email, handler;
async.waterfall([
function (cb) {r.post('/passwordreset').type('json').send({user:email}).expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
r.put('/passwordreset/'+res.user).type("json").set({Authorization:"Bearer "+res.code}).expect(400,cb);
}
],done);
});
it('should fail for known email with good code but missing new password with handler', function(done){
var email = users["1"].email, handler;
async.waterfall([
function (cb) {r.post('/passwordresetnext').type('json').send({user:email}).expect('activator','createResetHandler').expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
r.put('/passwordresetnext/'+res.user).type("json").set({Authorization:"Bearer "+res.code}).expect('activator','completeResetHandler').expect(400,cb);
}
],done);
});
it('should fail for expired reset code', function(done){
var user = users["1"], email = user.email, handler;
async.waterfall([
function (cb) {r.post('/passwordreset').type('json').send({user:"1"}).expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
// create a new code but signed with a different time
var code = changeResetTime(res.code,-100);
r.put('/passwordreset/'+res.user).set({"Authorization":"Bearer "+code}).type("json").send({password:"abcdefgh"}).expect(400,cb);
}
],done);
});
it('should fail for expired reset code with handler', function(done){
var user = users["1"], email = user.email, handler;
async.waterfall([
function (cb) {r.post('/passwordresetnext').type('json').send({user:"1"}).expect('activator','createResetHandler').expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
// create a new code but signed with a different time
var code = changeResetTime(res.code,-100);
r.put('/passwordresetnext/'+res.user).set({"Authorization":"Bearer "+code}).type("json").send({password:"abcdefgh"}).expect('activator','completeResetHandler').expect(400,cb);
}
],done);
});
it('should fail when trying to update password of another user', function(done){
var user = users["1"], email = user.email, handler;
async.waterfall([
function (cb) {r.post('/passwordreset').type('json').send({user:"1"}).expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
createUser({}, {}, function() {
mail.unbind(email,handler);
r.put('/passwordreset/'+users["2"].id).set({"Authorization":"Bearer "+res.code}).type('json').send({password:"abcdefgh"}).expect(400,cb);
});
}
],done);
});
it('should fail when trying to update password of another user with handler', function(done){
var user = users["1"], email = user.email, handler;
async.waterfall([
function (cb) {r.post('/passwordresetnext').type('json').send({user:"1"}).expect('activator','createResetHandler').expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
createUser({}, {}, function() {
mail.unbind(email,handler);
r.put('/passwordresetnext/'+users["2"].id).set({"Authorization":"Bearer "+res.code}).type('json').send({password:"abcdefgh"}).expect('activator','completeResetHandler').expect(400,cb);
});
}
],done);
});
it('should succeed for known ID', function(done){
var email = users["1"].email, handler, newpass = "abcdefgh";
async.waterfall([
function (cb) {r.post('/passwordreset').type('json').send({user:"1"}).expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
mail.removeAll();
r.put('/passwordreset/'+res.user).set({"Authorization":"Bearer "+res.code}).type("json").send({password:newpass}).expect(200,cb);
},
function (res,cb) {
handler = cHandler(email,{password:newpass},cb); mail.bind(email,handler);
},
function (res,cb) {
mail.unbind(email,handler);
mail.removeAll();
cb();
}
],done);
});
it('should succeed for query authentication over basic header', function(done){
var email = users["1"].email, handler, newpass = "abcdefgh";
async.waterfall([
function (cb) {r.post('/passwordreset').type('json').send({user:"1"}).expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
mail.removeAll();
r.put('/passwordreset/'+res.user).set({"Authorization":"Basic foo"}).query({authorization:res.code}).type("json").send({password:newpass}).expect(200,cb);
},
function (res,cb) {
handler = cHandler(email,{password:newpass},cb); mail.bind(email,handler);
},
function (res,cb) {
mail.unbind(email,handler);
mail.removeAll();
cb();
}
],done);
});
it('should succeed for known ID with handler', function(done){
var email = users["1"].email, handler, newpass = "a1111bcdefgh";
async.waterfall([
function (cb) {r.post('/passwordresetnext').type('json').send({user:"1"}).expect('activator','createResetHandler').expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
mail.removeAll();
r.put('/passwordresetnext/'+res.user).set({"Authorization":"Bearer "+res.code}).type("json").send({password:newpass}).expect('activator','completeResetHandler').expect(200,cb);
},
function (res,cb) {
handler = cHandler(email,{password:newpass},cb); mail.bind(email,handler);
},
function (res,cb) {
mail.unbind(email,handler);
mail.removeAll();
cb();
}
],done);
});
it('should succeed for known email', function(done){
var email = users["1"].email, handler, newpass = "abcdef7777gh";
async.waterfall([
function (cb) {r.post('/passwordreset').type('json').send({user:email}).expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
mail.removeAll();
// should have no attachments
should(res.content.attachments).be.empty();
r.put('/passwordreset/'+res.user).set({"Authorization":"Bearer "+res.code}).type("json").send({password:newpass}).expect(200,cb);
},
function (res,cb) {
handler = cHandler(email,{password:newpass},cb); mail.bind(email,handler);
},
function (res,cb) {
mail.unbind(email,handler);
mail.removeAll();
cb();
}
],done);
});
it('should succeed for known email with handler', function(done){
var email = users["1"].email, handler, newpass = "abcdef343434gh";
async.waterfall([
function (cb) {r.post('/passwordresetnext').type('json').send({user:email}).expect('activator','createResetHandler').expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
mail.removeAll();
r.put('/passwordresetnext/'+res.user).set({"Authorization":"Bearer "+res.code}).type("json").send({password:newpass}).expect('activator','completeResetHandler').expect(200,cb);
},
function (res,cb) {
handler = cHandler(email,{password:newpass},cb); mail.bind(email,handler);
},
function (res,cb) {
mail.unbind(email,handler);
mail.removeAll();
cb();
}
],done);
});
});
describe('auth query', function(){
it('should fail for known email but bad code', function(done){
var email = users["1"].email, handler;
async.waterfall([
function (cb) {
r.post('/passwordreset').type('json').send({user:email}).expect(201,cb);},
function (res,cb) {
handler = rHandler(email,cb);
mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
r.put('/passwordreset/'+res.user).query({"Authorization":"asasqsqsas"}).type("json").send({password:"asasa"}).expect(400,cb);
}
],done);
});
it('should fail for known email but bad code with handler', function(done){
var email = users["1"].email, handler;
async.waterfall([
function (cb) {
r.post('/passwordresetnext').type('json').send({user:email}).expect('activator','createResetHandler').expect(201,cb);},
function (res,cb) {
handler = rHandler(email,cb);
mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
r.put('/passwordresetnext/'+res.user).query({Authorization:"asasqsqsqs"}).type("json").send({password:"asasa"}).expect('activator','completeResetHandler').expect(400,cb);
}
],done);
});
it('should fail for known email with good code but missing new password', function(done){
var email = users["1"].email, handler;
async.waterfall([
function (cb) {r.post('/passwordreset').type('json').send({user:email}).expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
r.put('/passwordreset/'+res.user).query({"Authorization":res.code}).type("json").send({}).expect(400,cb);
}
],done);
});
it('should fail for known email with good code but missing new password with handler', function(done){
var email = users["1"].email, handler;
async.waterfall([
function (cb) {r.post('/passwordresetnext').type('json').send({user:email}).expect('activator','createResetHandler').expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
r.put('/passwordresetnext/'+res.user).query({"Authorization":res.code}).type("json").send({}).expect('activator','completeResetHandler').expect(400,cb);
}
],done);
});
it('should fail for expired reset code', function(done){
var user = users["1"], email = user.email, handler;
async.waterfall([
function (cb) {r.post('/passwordreset').type('json').send({user:"1"}).expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
// create a new code but signed with a different time
var code = changeResetTime(res.code,-100);
r.put('/passwordreset/'+res.user).query({Authorization:code}).type("json").send({password:"abcdefgh"}).expect(400,cb);
}
],done);
});
it('should fail for expired reset code with handler', function(done){
var user = users["1"], email = user.email, handler;
async.waterfall([
function (cb) {r.post('/passwordresetnext').type('json').send({user:"1"}).expect('activator','createResetHandler').expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
// create a new code but signed with a different time
var code = changeResetTime(res.code,-100);
r.put('/passwordresetnext/'+res.user).query({Authorization:code}).type("json").send({password:"abcdefgh"}).expect('activator','completeResetHandler').expect(400,cb);
}
],done);
});
it('should fail when trying to update password of another user', function(done){
var user = users["1"], email = user.email, handler;
async.waterfall([
function (cb) {r.post('/passwordreset').type('json').send({user:"1"}).expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
createUser({}, {}, function() {
mail.unbind(email,handler);
r.put('/passwordreset/'+users["2"].id).query({Authorization:res.code}).type('json').send({password:"abcdefgh"}).expect(400,cb);
});
}
],done);
});
it('should fail when trying to update password of another user with handler', function(done){
var user = users["1"], email = user.email, handler;
async.waterfall([
function (cb) {r.post('/passwordresetnext').type('json').send({user:"1"}).expect('activator','createResetHandler').expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
createUser({}, {}, function() {
mail.unbind(email,handler);
r.put('/passwordresetnext/'+users["2"].id).query({Authorization:res.code}).type('json').send({password:"abcdefgh"}).expect('activator','completeResetHandler').expect(400,cb);
});
}
],done);
});
it('should succeed for known ID', function(done){
var email = users["1"].email, handler;
async.waterfall([
function (cb) {r.post('/passwordreset').type('json').send({user:"1"}).expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
r.put('/passwordreset/'+res.user).query({Authorization:res.code}).type("json").send({password:"abcdefgh"}).expect(200,cb);
}
],done);
});
it('should succeed for known ID with handler', function(done){
var email = users["1"].email, handler;
async.waterfall([
function (cb) {r.post('/passwordresetnext').type('json').send({user:"1"}).expect('activator','createResetHandler').expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
r.put('/passwordresetnext/'+res.user).query({Authorization:res.code}).type("json").send({password:"abcdefgh"}).expect('activator','completeResetHandler').expect(200,cb);
}
],done);
});
it('should succeed for known email', function(done){
var email = users["1"].email, handler;
async.waterfall([
function (cb) {r.post('/passwordreset').type('json').send({user:email}).expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
// should have no attachments
should(res.content.attachments).be.empty();
r.put('/passwordreset/'+res.user).query({Authorization:res.code}).type("json").send({password:"abcdefgh"}).expect(200,cb);
}
],done);
});
it('should succeed for known email with handler', function(done){
var email = users["1"].email, handler;
async.waterfall([
function (cb) {r.post('/passwordresetnext').type('json').send({user:email}).expect('activator','createResetHandler').expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
r.put('/passwordresetnext/'+res.user).query({Authorization:res.code}).type("json").send({password:"abcdefgh"}).expect('activator','completeResetHandler').expect(200,cb);
}
],done);
});
});
describe('auth body', function(){
it('should fail for known email but bad code', function(done){
var email = users["1"].email, handler;
async.waterfall([
function (cb) {
r.post('/passwordreset').type('json').send({user:email}).expect(201,cb);},
function (res,cb) {
handler = rHandler(email,cb);
mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
r.put('/passwordreset/'+res.user).type("json").send({Authorization:"asasqsqsqs",password:"asasa"}).expect(400,cb);
}
],done);
});
it('should fail for known email but bad code with handler', function(done){
var email = users["1"].email, handler;
async.waterfall([
function (cb) {
r.post('/passwordresetnext').type('json').send({user:email}).expect('activator','createResetHandler').expect(201,cb);},
function (res,cb) {
handler = rHandler(email,cb);
mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
r.put('/passwordresetnext/'+res.user).type("json").send({Authorization:"asasqsqsqs",password:"asasa"}).expect('activator','completeResetHandler').expect(400,cb);
}
],done);
});
it('should fail for known email with good code but missing new password', function(done){
var email = users["1"].email, handler;
async.waterfall([
function (cb) {r.post('/passwordreset').type('json').send({user:email}).expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
r.put('/passwordreset/'+res.user).type("json").send({Authorization:res.code}).expect(400,cb);
}
],done);
});
it('should fail for known email with good code but missing new password with handler', function(done){
var email = users["1"].email, handler;
async.waterfall([
function (cb) {r.post('/passwordresetnext').type('json').send({user:email}).expect('activator','createResetHandler').expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
r.put('/passwordresetnext/'+res.user).type("json").send({Authorization:res.code}).expect('activator','completeResetHandler').expect(400,cb);
}
],done);
});
it('should fail for expired reset code', function(done){
var user = users["1"], email = user.email, handler;
async.waterfall([
function (cb) {r.post('/passwordreset').type('json').send({user:"1"}).expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
// create a new code but signed with a different time
var code = changeResetTime(res.code,-100);
r.put('/passwordreset/'+res.user).type("json").send({Authorization:code,password:"abcdefgh"}).expect(400,cb);
}
],done);
});
it('should fail for expired reset code with handler', function(done){
var user = users["1"], email = user.email, handler;
async.waterfall([
function (cb) {r.post('/passwordresetnext').type('json').send({user:"1"}).expect('activator','createResetHandler').expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
// create a new code but signed with a different time
var code = changeResetTime(res.code,-100);
r.put('/passwordresetnext/'+res.user).type("json").send({Authorization:code,password:"abcdefgh"}).expect('activator','completeResetHandler').expect(400,cb);
}
],done);
});
it('should fail when trying to update password of another user', function(done){
var user = users["1"], email = user.email, handler;
async.waterfall([
function (cb) {r.post('/passwordreset').type('json').send({user:"1"}).expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
createUser({}, {}, function() {
mail.unbind(email,handler);
r.put('/passwordreset/'+users["2"].id).type('json').send({Authorization:res.code,password:"abcdefgh"}).expect(400,cb);
});
}
],done);
});
it('should fail when trying to update password of another user with handler', function(done){
var user = users["1"], email = user.email, handler;
async.waterfall([
function (cb) {r.post('/passwordresetnext').type('json').send({user:"1"}).expect('activator','createResetHandler').expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
createUser({}, {}, function() {
mail.unbind(email,handler);
r.put('/passwordresetnext/'+users["2"].id).type('json').send({Authorization:res.code,password:"abcdefgh"}).expect('activator','completeResetHandler').expect(400,cb);
});
}
],done);
});
it('should succeed for known ID', function(done){
var email = users["1"].email, handler;
async.waterfall([
function (cb) {r.post('/passwordreset').type('json').send({user:"1"}).expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
r.put('/passwordreset/'+res.user).type("json").send({Authorization:res.code,password:"abcdefgh"}).expect(200,cb);
}
],done);
});
it('should succeed for known ID with handler', function(done){
var email = users["1"].email, handler;
async.waterfall([
function (cb) {r.post('/passwordresetnext').type('json').send({user:"1"}).expect('activator','createResetHandler').expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
r.put('/passwordresetnext/'+res.user).type("json").send({Authorization:res.code,password:"abcdefgh"}).expect('activator','completeResetHandler').expect(200,cb);
}
],done);
});
it('should succeed for known email', function(done){
var email = users["1"].email, handler;
async.waterfall([
function (cb) {r.post('/passwordreset').type('json').send({user:email}).expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
// should have no attachments
should(res.content.attachments).be.empty();
r.put('/passwordreset/'+res.user).type("json").send({Authorization:res.code,password:"abcdefgh"}).expect(200,cb);
}
],done);
});
it('should succeed for known email with handler', function(done){
var email = users["1"].email, handler;
async.waterfall([
function (cb) {r.post('/passwordresetnext').type('json').send({user:email}).expect('activator','createResetHandler').expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
r.put('/passwordresetnext/'+res.user).type("json").send({Authorization:res.code,password:"abcdefgh"}).expect('activator','completeResetHandler').expect(200,cb);
}
],done);
});
});
});
describe('with attachments', function(){
var attachments = {
activate: [
{ // utf-8 string as an attachment
filename: 'activate1.txt',
content: 'hello activate!'
},
{ // binary buffer as an attachment
filename: 'activate2.txt',
content: new Buffer('goodbye activate!','utf-8')
}
],
passwordreset: [
{ // utf-8 string as an attachment
filename: 'reset1.txt',
content: 'hello reset!'
},
{ // binary buffer as an attachment
filename: 'reset2.txt',
content: new Buffer('goodbye reset!','utf-8')
}
]
};
before(function(){
activator.init({user:userModel,transport:url,templates:templates,from:from,attachments:attachments,signkey:SIGNKEY});
});
it('should include correct attachment for activate', function(done){
var email, handler;
async.waterfall([
function (cb) {r.post('/users').expect(201,cb);},
function (res,cb) {
res.text.should.equal("2");
email = users["2"].email;
handler = aHandler(email,cb);
mail.bind(email,handler);
},
function (res,cb) {
var att = res.content.attachments, exp = attachments.activate;
mail.unbind(email,handler);
// check there is an attachment
should(att).be.ok();
// check the attachment matches
att.length.should.eql(exp.length);
att[0].filename.should.eql(exp[0].filename);
att[0].content.toString().should.eql(exp[0].content.toString());
att[1].filename.should.eql(exp[1].filename);
att[1].content.toString().should.eql(exp[1].content.toString());
cb();
}
],done);
});
it('should include correct attachment for passwordreset', function(done){
var email = users["1"].email, handler;
async.waterfall([
function (cb) {r.post('/passwordresetnext').type('json').send({user:email}).expect('activator','createResetHandler').expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
var att = res.content.attachments, exp = attachments.passwordreset;
mail.unbind(email,handler);
// check there is an attachment
should(att).be.ok();
// check the attachment matches
att.length.should.eql(exp.length);
att[0].filename.should.eql(exp[0].filename);
att[0].content.toString().should.eql(exp[0].content.toString());
att[1].filename.should.eql(exp[1].filename);
att[1].content.toString().should.eql(exp[1].content.toString());
cb();
}
],done);
});
});
describe('with styliner property', function() {
before(function(){
activator.init({user:userModel,transport:url,templates:htmlTemplates,from:from,styliner:true,signkey:SIGNKEY});
});
it('should inline style tags', function(done) {
var email, handler;
async.waterfall([
function (cb) {r.post('/users').expect(201,cb);},
function (res,cb) {
res.text.should.equal("2");
email = users["2"].email;
handler = aHandler(email,cb);
mail.bind(email,handler);
},
function (res,cb) {
mail.unbind(email,handler);
res.content.html.match(/style="background: blue;"/)[0].should.be.ok();
cb();
}
],done);
});
});
describe('with email property override', function(){
before(function(){
activator.init({user:userModelEmail,emailProperty:"funny",transport:url,templates:templates,from:from,signkey:SIGNKEY});
});
it('activate should succeed for known user', function(done){
var email, handler;
async.waterfall([
function (cb) {r.post('/users').expect(201,cb);},
function (res,cb) {
res.text.should.equal("2");
email = users["2"].email;
handler = aHandler(email,cb);
mail.bind(email,handler);
},
function (res,cb) {
mail.unbind(email,handler);
r.put('/users/'+res.user+'/activate').type("json").send({Authorization:res.code}).expect(200,cb);
}
],done);
});
it('password reset should succeed for known email', function(done){
var email = users["1"].email, handler;
async.waterfall([
function (cb) {r.post('/passwordreset').type('json').send({user:email}).expect(201,cb);},
function (res,cb) {handler = rHandler(email,cb); mail.bind(email,handler);},
function (res,cb) {
mail.unbind(email,handler);
r.put('/passwordreset/'+res.user).type("json").send({Authorization:res.code,password:"abcdefgh"}).expect(200,cb);
}
],done);
});
});
describe('with email property override in model child object', function(){
it('activate should succeed for known user', function(done){
var email, handler;
async.waterfall([
function (cb) {
activator.init({user:userModelEmail,emailProperty:"childObject.funny",transport:url,templates:templates,from:from,signkey:SIGNKEY});
r.post('/users').expect(201,cb);
},
function (res,cb) {
res.text.should.equal("2");
email = users["2"].email;
handler = aHandler(email,cb);
mail.bind(email,handler);
},
function (res,cb) {
mail.unbind(email,handler);
r.put('/users/'+res.user+'/activate').type("json").send({Authorization:res.code}).expect(200,cb);
}
],done);
});
it('activate should fail on bad property', function(done){
activator.init({user:userModelEmail,emailProperty:"childObject.badPath.funny",transport:url,templates:templates,from:from,signkey:SIGNKEY});
r.post('/users').expect(400,done);
});
it('password reset should succeed for known email', functio