var User = loopback.User.extend('user'); var Session = loopback.Session; var passport = require('passport'); var userMemory = loopback.createDataSource({ connector: loopback.Memory }); describe('User', function(){ User.attachTo(userMemory); User.session.attachTo(userMemory); User.email.setup({transports: [{type: 'STUB'}]}); // allow many User.afterRemote's to be called User.setMaxListeners(0); beforeEach(function (done) { app.use(loopback.rest()); app.model(User); User.create({email: 'foo@bar.com', password: 'bar'}, done); }); afterEach(function (done) { User.destroyAll(function (err) { User.session.destroyAll(done); }); }); describe('User.create', function(){ it('Create a new user', function(done) { User.create({email: 'f@b.com', password: 'bar'}, function (err, user) { assert(!err); assert(user.id); assert(user.email); done(); }); }); it('Email is required', function(done) { User.create({password: '123'}, function (err) { assert.deepEqual(err, { name: 'ValidationError', message: 'Validation error', statusCode: 400, codes: { email: [ 'presence', 'format.blank' ] }, context: 'user' }); 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(); }); }); it('Requires a valid email', function(done) { User.create({email: 'foo@', password: '123'}, function (err) { assert(err); done(); }); }); it('Requires a unique email', function(done) { User.create({email: 'a@b.com', password: 'foobar'}, function () { User.create({email: 'a@b.com', password: 'batbaz'}, function (err) { assert(err, 'should error because the email is not unique!'); done(); }); }); }); it('Requires a password to login with basic auth', function(done) { User.create({email: 'b@c.com'}, function (err) { User.login({email: 'b@c.com'}, function (err, session) { assert(!session, 'should not create a session without a valid password'); assert(err, 'should not login without a password'); done(); }); }); }); it('Hashes the given password', function() { var u = new User({username: 'foo', password: 'bar'}); assert(u.password !== 'bar'); }); }); describe('User.login', function() { it('Login a user by providing credentials', function(done) { User.login({email: 'foo@bar.com', password: 'bar'}, function (err, session) { assert(session.uid); assert(session.id); assert.equal((new Buffer(session.id, 'base64')).length, 64); done(); }); }); it('Login a user over REST by providing credentials', function(done) { 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); var session = res.body; assert(session.uid); assert(session.id); assert.equal((new Buffer(session.id, 'base64')).length, 64); done(); }); }); it('Login should only allow correct credentials', function(done) { User.create({email: 'foo22@bar.com', password: 'bar'}, function(user, err) { User.login({email: 'foo44@bar.com', password: 'bar'}, function(err, session) { assert(err); assert(!session); done(); }); }); }); }); describe('User.logout', function() { it('Logout a user by providing the current session id (using node)', function(done) { login(logout); function login(fn) { User.login({email: 'foo@bar.com', password: 'bar'}, fn); } function logout(err, session) { User.logout(session.id, verify(session.id, done)); } }); it('Logout a user by providing the current session id (over rest)', function(done) { 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); var session = res.body; assert(session.uid); assert(session.id); fn(null, session.id); }); } function logout(err, sid) { request(app) .post('/users/logout') .expect(204) .send({sid: sid}) .end(verify(sid, done)); } }); function verify(sid, done) { assert(sid); return function (err) { if(err) return done(err); Session.findById(sid, function (err, session) { assert(!session, 'session should not exist after logging out'); done(err); }); } } }); describe('user.hasPassword(plain, fn)', function(){ it('Determine if the password matches the stored password', function(done) { var u = new User({username: 'foo', password: 'bar'}); u.hasPassword('bar', function (err, isMatch) { assert(isMatch, 'password doesnt match'); done(); }); }); 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(); }); }); }); }); 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(); }); }); }); }); }); }); }); }); }); 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); var lines = result.email.message.split('\n'); assert(lines[4].indexOf('To: bar@bat.com') === 0); done(); }); }); request(app) .post('/users') .expect('Content-Type', /json/) .expect(200) .send({email: 'bar@bat.com', password: 'bar'}) .end(function(err, res){ if(err) return done(err); }); }); }); describe('User.confirm(options, fn)', function(){ 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) .send({email: 'bar@bat.com', password: 'bar'}) .end(function(err, res){ if(err) return done(err); }); }); }); }); });