2013-07-16 17:49:25 +00:00
|
|
|
var User = loopback.User.extend('user');
|
2013-11-13 19:49:08 +00:00
|
|
|
var AccessToken = loopback.AccessToken;
|
2013-07-02 23:51:38 +00:00
|
|
|
var passport = require('passport');
|
2013-10-12 02:06:16 +00:00
|
|
|
var MailConnector = require('../lib/connectors/mail');
|
2013-07-02 23:51:38 +00:00
|
|
|
|
2013-07-16 17:49:25 +00:00
|
|
|
var userMemory = loopback.createDataSource({
|
|
|
|
connector: loopback.Memory
|
2013-07-03 05:37:31 +00:00
|
|
|
});
|
2013-07-15 17:56:42 +00:00
|
|
|
|
2013-07-02 23:51:38 +00:00
|
|
|
describe('User', function(){
|
2013-07-16 01:22:33 +00:00
|
|
|
// allow many User.afterRemote's to be called
|
2013-07-28 20:20:55 +00:00
|
|
|
User.setMaxListeners(0);
|
2013-07-02 23:51:38 +00:00
|
|
|
|
2013-11-19 18:29:02 +00:00
|
|
|
before(function () {
|
|
|
|
User.hasMany(AccessToken, {as: 'accessTokens', foreignKey: 'userId'});
|
2013-11-20 18:59:29 +00:00
|
|
|
AccessToken.belongsTo(User);
|
2013-11-19 18:29:02 +00:00
|
|
|
});
|
2013-11-19 00:13:40 +00:00
|
|
|
|
2013-07-02 23:51:38 +00:00
|
|
|
beforeEach(function (done) {
|
2013-07-16 17:49:25 +00:00
|
|
|
app.use(loopback.rest());
|
2013-07-16 01:22:33 +00:00
|
|
|
app.model(User);
|
2013-07-02 23:51:38 +00:00
|
|
|
|
2013-07-16 01:22:33 +00:00
|
|
|
User.create({email: 'foo@bar.com', password: 'bar'}, done);
|
2013-07-02 23:51:38 +00:00
|
|
|
});
|
|
|
|
|
2013-07-03 05:37:31 +00:00
|
|
|
afterEach(function (done) {
|
2013-07-16 01:22:33 +00:00
|
|
|
User.destroyAll(function (err) {
|
2013-11-13 19:49:08 +00:00
|
|
|
User.accessToken.destroyAll(done);
|
2013-07-13 00:03:13 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('User.create', function(){
|
2013-07-16 20:41:17 +00:00
|
|
|
it('Create a new user', function(done) {
|
2013-07-28 20:20:55 +00:00
|
|
|
User.create({email: 'f@b.com', password: 'bar'}, function (err, user) {
|
2013-07-13 00:03:13 +00:00
|
|
|
assert(!err);
|
|
|
|
assert(user.id);
|
|
|
|
assert(user.email);
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
2013-12-04 22:41:25 +00:00
|
|
|
|
|
|
|
it('Email is required', function (done) {
|
2013-07-28 20:20:55 +00:00
|
|
|
User.create({password: '123'}, function (err) {
|
2013-12-04 22:41:25 +00:00
|
|
|
assert.deepEqual(err, {name: "ValidationError",
|
|
|
|
message: "The Model instance is not valid. See `details` "
|
|
|
|
+ "property of the error object for more info.",
|
|
|
|
statusCode: 422,
|
|
|
|
details: {
|
|
|
|
context: "user",
|
|
|
|
codes: {email: ["presence", "format.blank", "uniqueness"]},
|
|
|
|
messages: {email: ["can't be blank", "is blank",
|
|
|
|
"Email already exists"]}}}
|
|
|
|
);
|
|
|
|
|
2013-07-28 20:20:55 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
// will change in future versions where password will be optional by default
|
|
|
|
it('Password is required', function(done) {
|
|
|
|
var u = new User({email: "123@456.com"});
|
|
|
|
|
|
|
|
User.create({email: 'c@d.com'}, function (err) {
|
|
|
|
assert(err);
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2013-07-16 20:41:17 +00:00
|
|
|
it('Requires a valid email', function(done) {
|
2013-07-28 20:20:55 +00:00
|
|
|
User.create({email: 'foo@', password: '123'}, function (err) {
|
2013-07-13 00:03:13 +00:00
|
|
|
assert(err);
|
2013-07-28 20:20:55 +00:00
|
|
|
done();
|
2013-07-13 00:03:13 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2013-07-16 20:41:17 +00:00
|
|
|
it('Requires a unique email', function(done) {
|
2013-07-28 20:20:55 +00:00
|
|
|
User.create({email: 'a@b.com', password: 'foobar'}, function () {
|
|
|
|
User.create({email: 'a@b.com', password: 'batbaz'}, function (err) {
|
2013-07-13 00:03:13 +00:00
|
|
|
assert(err, 'should error because the email is not unique!');
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2013-07-16 20:41:17 +00:00
|
|
|
it('Requires a password to login with basic auth', function(done) {
|
2013-07-13 00:03:13 +00:00
|
|
|
User.create({email: 'b@c.com'}, function (err) {
|
2013-11-13 19:49:08 +00:00
|
|
|
User.login({email: 'b@c.com'}, function (err, accessToken) {
|
|
|
|
assert(!accessToken, 'should not create a accessToken without a valid password');
|
2013-07-13 00:03:13 +00:00
|
|
|
assert(err, 'should not login without a password');
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2013-07-16 01:22:33 +00:00
|
|
|
|
2013-07-16 20:41:17 +00:00
|
|
|
it('Hashes the given password', function() {
|
2013-07-16 01:22:33 +00:00
|
|
|
var u = new User({username: 'foo', password: 'bar'});
|
|
|
|
assert(u.password !== 'bar');
|
|
|
|
});
|
2013-07-03 05:37:31 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
describe('User.login', function() {
|
2013-07-16 20:41:17 +00:00
|
|
|
it('Login a user by providing credentials', function(done) {
|
2013-11-13 19:49:08 +00:00
|
|
|
User.login({email: 'foo@bar.com', password: 'bar'}, function (err, accessToken) {
|
2013-11-15 02:34:51 +00:00
|
|
|
assert(accessToken.userId);
|
2013-11-13 19:49:08 +00:00
|
|
|
assert(accessToken.id);
|
2013-11-14 21:01:47 +00:00
|
|
|
assert.equal(accessToken.id.length, 64);
|
2013-07-28 20:20:55 +00:00
|
|
|
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Login a user over REST by providing credentials', function(done) {
|
2013-07-02 23:51:38 +00:00
|
|
|
request(app)
|
|
|
|
.post('/users/login')
|
|
|
|
.expect('Content-Type', /json/)
|
|
|
|
.expect(200)
|
|
|
|
.send({email: 'foo@bar.com', password: 'bar'})
|
|
|
|
.end(function(err, res){
|
|
|
|
if(err) return done(err);
|
2013-11-13 19:49:08 +00:00
|
|
|
var accessToken = res.body;
|
2013-07-02 23:51:38 +00:00
|
|
|
|
2013-11-15 02:34:51 +00:00
|
|
|
assert(accessToken.userId);
|
2013-11-13 19:49:08 +00:00
|
|
|
assert(accessToken.id);
|
2013-11-14 21:01:47 +00:00
|
|
|
assert.equal(accessToken.id.length, 64);
|
2013-07-02 23:51:38 +00:00
|
|
|
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
2013-07-28 21:33:13 +00:00
|
|
|
|
|
|
|
it('Login should only allow correct credentials', function(done) {
|
|
|
|
User.create({email: 'foo22@bar.com', password: 'bar'}, function(user, err) {
|
2013-11-13 19:49:08 +00:00
|
|
|
User.login({email: 'foo44@bar.com', password: 'bar'}, function(err, accessToken) {
|
2013-07-28 21:33:13 +00:00
|
|
|
assert(err);
|
2013-11-13 19:49:08 +00:00
|
|
|
assert(!accessToken);
|
2013-07-28 21:33:13 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2013-07-02 23:51:38 +00:00
|
|
|
});
|
2013-07-03 05:37:31 +00:00
|
|
|
|
|
|
|
describe('User.logout', function() {
|
2013-11-13 19:49:08 +00:00
|
|
|
it('Logout a user by providing the current accessToken id (using node)', function(done) {
|
2013-07-12 23:10:15 +00:00
|
|
|
login(logout);
|
|
|
|
|
|
|
|
function login(fn) {
|
|
|
|
User.login({email: 'foo@bar.com', password: 'bar'}, fn);
|
|
|
|
}
|
|
|
|
|
2013-11-13 19:49:08 +00:00
|
|
|
function logout(err, accessToken) {
|
|
|
|
User.logout(accessToken.id, verify(accessToken.id, done));
|
2013-07-12 23:10:15 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2013-11-13 19:49:08 +00:00
|
|
|
it('Logout a user by providing the current accessToken id (over rest)', function(done) {
|
2013-07-03 05:37:31 +00:00
|
|
|
login(logout);
|
|
|
|
|
|
|
|
function login(fn) {
|
|
|
|
request(app)
|
|
|
|
.post('/users/login')
|
|
|
|
.expect('Content-Type', /json/)
|
|
|
|
.expect(200)
|
|
|
|
.send({email: 'foo@bar.com', password: 'bar'})
|
|
|
|
.end(function(err, res){
|
|
|
|
if(err) return done(err);
|
2013-11-13 19:49:08 +00:00
|
|
|
var accessToken = res.body;
|
2013-07-03 05:37:31 +00:00
|
|
|
|
2013-11-15 02:34:51 +00:00
|
|
|
assert(accessToken.userId);
|
2013-11-13 19:49:08 +00:00
|
|
|
assert(accessToken.id);
|
2013-07-03 05:37:31 +00:00
|
|
|
|
2013-11-13 19:49:08 +00:00
|
|
|
fn(null, accessToken.id);
|
2013-07-03 05:37:31 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function logout(err, sid) {
|
|
|
|
request(app)
|
|
|
|
.post('/users/logout')
|
2013-07-25 00:21:15 +00:00
|
|
|
.expect(204)
|
2013-07-03 05:37:31 +00:00
|
|
|
.send({sid: sid})
|
2013-07-12 23:10:15 +00:00
|
|
|
.end(verify(sid, done));
|
2013-07-03 05:37:31 +00:00
|
|
|
}
|
2013-07-12 23:10:15 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
function verify(sid, done) {
|
|
|
|
assert(sid);
|
|
|
|
|
|
|
|
return function (err) {
|
|
|
|
if(err) return done(err);
|
|
|
|
|
2013-11-13 19:49:08 +00:00
|
|
|
AccessToken.findById(sid, function (err, accessToken) {
|
|
|
|
assert(!accessToken, 'accessToken should not exist after logging out');
|
2013-07-12 23:10:15 +00:00
|
|
|
done(err);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2013-07-03 05:37:31 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
describe('user.hasPassword(plain, fn)', function(){
|
2013-07-16 20:41:17 +00:00
|
|
|
it('Determine if the password matches the stored password', function(done) {
|
2013-07-03 05:37:31 +00:00
|
|
|
var u = new User({username: 'foo', password: 'bar'});
|
|
|
|
u.hasPassword('bar', function (err, isMatch) {
|
|
|
|
assert(isMatch, 'password doesnt match');
|
|
|
|
done();
|
2013-07-15 21:07:17 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2013-07-16 01:22:33 +00:00
|
|
|
it('should match a password when saved', function(done) {
|
|
|
|
var u = new User({username: 'a', password: 'b', email: 'z@z.net'});
|
|
|
|
|
|
|
|
u.save(function (err, user) {
|
|
|
|
User.findById(user.id, function (err, uu) {
|
|
|
|
uu.hasPassword('b', function (err, isMatch) {
|
|
|
|
assert(isMatch);
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2013-07-15 21:07:17 +00:00
|
|
|
it('should match a password after it is changed', function(done) {
|
|
|
|
User.create({email: 'foo@baz.net', username: 'bat', password: 'baz'}, function (err, user) {
|
|
|
|
User.findById(user.id, function (err, foundUser) {
|
|
|
|
assert(foundUser);
|
|
|
|
foundUser.hasPassword('baz', function (err, isMatch) {
|
|
|
|
assert(isMatch);
|
|
|
|
foundUser.password = 'baz2';
|
|
|
|
foundUser.save(function (err, updatedUser) {
|
|
|
|
updatedUser.hasPassword('baz2', function (err, isMatch) {
|
|
|
|
assert(isMatch);
|
|
|
|
User.findById(user.id, function (err, uu) {
|
|
|
|
uu.hasPassword('baz2', function (err, isMatch) {
|
|
|
|
assert(isMatch);
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2013-07-03 05:37:31 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('Verification', function(){
|
|
|
|
|
|
|
|
describe('user.verify(options, fn)', function(){
|
|
|
|
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) {
|
|
|
|
assert(result.email);
|
|
|
|
assert(result.email.message);
|
|
|
|
assert(result.token);
|
|
|
|
|
|
|
|
|
2013-11-15 04:19:46 +00:00
|
|
|
assert(~result.email.message.indexOf('To: bar@bat.com'));
|
2013-07-03 05:37:31 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
request(app)
|
|
|
|
.post('/users')
|
|
|
|
.expect('Content-Type', /json/)
|
|
|
|
.expect(200)
|
2013-07-15 17:56:42 +00:00
|
|
|
.send({email: 'bar@bat.com', password: 'bar'})
|
2013-07-03 05:37:31 +00:00
|
|
|
.end(function(err, res){
|
|
|
|
if(err) return done(err);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('User.confirm(options, fn)', function(){
|
|
|
|
it('Confirm a user verification', 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: 'http://foo.com/bar',
|
|
|
|
protocol: ctx.req.protocol,
|
|
|
|
host: ctx.req.get('host')
|
|
|
|
};
|
|
|
|
|
|
|
|
user.verify(options, function (err, result) {
|
|
|
|
if(err) return done(err);
|
|
|
|
|
|
|
|
request(app)
|
|
|
|
.get('/users/confirm?uid=' + result.uid + '&token=' + encodeURIComponent(result.token) + '&redirect=' + encodeURIComponent(options.redirect))
|
|
|
|
.expect(302)
|
|
|
|
.expect('location', options.redirect)
|
|
|
|
.end(function(err, res){
|
|
|
|
if(err) return done(err);
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
request(app)
|
|
|
|
.post('/users')
|
|
|
|
.expect('Content-Type', /json/)
|
|
|
|
.expect(302)
|
2013-07-15 17:56:42 +00:00
|
|
|
.send({email: 'bar@bat.com', password: 'bar'})
|
2013-07-03 05:37:31 +00:00
|
|
|
.end(function(err, res){
|
|
|
|
if(err) return done(err);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2013-11-20 18:59:29 +00:00
|
|
|
|
|
|
|
describe('Password Reset', function () {
|
|
|
|
describe('User.resetPassword(options, cb)', function () {
|
|
|
|
it('Creates a temp accessToken to allow a user to change password', function (done) {
|
|
|
|
var calledBack = false;
|
|
|
|
var email = 'foo@bar.com';
|
|
|
|
|
|
|
|
User.resetPassword({
|
|
|
|
email: email
|
|
|
|
}, function () {
|
|
|
|
calledBack = true;
|
|
|
|
});
|
|
|
|
|
|
|
|
User.once('resetPasswordRequest', function (info) {
|
|
|
|
assert(info.email);
|
|
|
|
assert(info.accessToken);
|
|
|
|
assert(info.accessToken.id);
|
|
|
|
assert.equal(info.accessToken.ttl / 60, 15);
|
|
|
|
assert(calledBack);
|
|
|
|
info.accessToken.user(function (err, user) {
|
|
|
|
assert.equal(user.email, email);
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2013-10-11 20:37:51 +00:00
|
|
|
});
|