Promisify User model

This commit is contained in:
Pradnya Baviskar 2015-07-01 17:13:25 +05:30
parent 9422175510
commit dc987a59a9
4 changed files with 236 additions and 3 deletions

View File

@ -3,6 +3,7 @@
*/
var loopback = require('../../lib/loopback');
var utils = require('../../lib/utils');
var path = require('path');
var SALT_WORK_FACTOR = 10;
var crypto = require('crypto');
@ -80,6 +81,9 @@ module.exports = function(User) {
cb = options;
options = undefined;
}
cb = cb || utils.createPromiseCallback();
if (typeof ttl === 'object' && !options) {
// createAccessToken(options, cb)
options = ttl;
@ -91,6 +95,7 @@ module.exports = function(User) {
this.accessTokens.create({
ttl: ttl
}, cb);
return cb.promise;
};
function splitPrincipal(name, realmDelimiter) {
@ -168,6 +173,8 @@ module.exports = function(User) {
include = undefined;
}
fn = fn || utils.createPromiseCallback();
include = (include || '');
if (Array.isArray(include)) {
include = include.map(function(val) {
@ -191,13 +198,15 @@ module.exports = function(User) {
var err1 = new Error('realm is required');
err1.statusCode = 400;
err1.code = 'REALM_REQUIRED';
return fn(err1);
fn(err1);
return fn.promise;
}
if (!query.email && !query.username) {
var err2 = new Error('username or email is required');
err2.statusCode = 400;
err2.code = 'USERNAME_EMAIL_REQUIRED';
return fn(err2);
fn(err2);
return fn.promise;
}
self.findOne({where: query}, function(err, user) {
@ -234,7 +243,7 @@ module.exports = function(User) {
err = new Error('login failed as the email has not been verified');
err.statusCode = 401;
err.code = 'LOGIN_FAILED_EMAIL_NOT_VERIFIED';
return fn(err);
fn(err);
} else {
if (user.createAccessToken.length === 2) {
user.createAccessToken(credentials.ttl, tokenHandler);
@ -252,6 +261,7 @@ module.exports = function(User) {
fn(defaultError);
}
});
return fn.promise;
};
/**
@ -269,6 +279,7 @@ module.exports = function(User) {
*/
User.logout = function(tokenId, fn) {
fn = fn || utils.createPromiseCallback();
this.relations.accessTokens.modelTo.findById(tokenId, function(err, accessToken) {
if (err) {
fn(err);
@ -278,6 +289,7 @@ module.exports = function(User) {
fn(new Error('could not find accessToken'));
}
});
return fn.promise;
};
/**
@ -288,6 +300,7 @@ module.exports = function(User) {
*/
User.prototype.hasPassword = function(plain, fn) {
fn = fn || utils.createPromiseCallback();
if (this.password && plain) {
bcrypt.compare(plain, this.password, function(err, isMatch) {
if (err) return fn(err);
@ -296,6 +309,7 @@ module.exports = function(User) {
} else {
fn(null, false);
}
return fn.promise;
};
/**
@ -332,6 +346,8 @@ module.exports = function(User) {
*/
User.prototype.verify = function(options, fn) {
fn = fn || utils.createPromiseCallback();
var user = this;
var userModel = this.constructor;
var registry = userModel.registry;
@ -408,6 +424,7 @@ module.exports = function(User) {
}
});
}
return fn.promise;
};
/**
@ -436,6 +453,7 @@ module.exports = function(User) {
* @param {Error} err
*/
User.confirm = function(uid, token, redirect, fn) {
fn = fn || utils.createPromiseCallback();
this.findById(uid, function(err, user) {
if (err) {
fn(err);
@ -464,6 +482,7 @@ module.exports = function(User) {
}
}
});
return fn.promise;
};
/**
@ -477,6 +496,7 @@ module.exports = function(User) {
*/
User.resetPassword = function(options, cb) {
cb = cb || utils.createPromiseCallback();
var UserModel = this;
var ttl = UserModel.settings.resetPasswordTokenTTL || DEFAULT_RESET_PW_TTL;
@ -510,6 +530,7 @@ module.exports = function(User) {
err.code = 'EMAIL_REQUIRED';
cb(err);
}
return cb.promise;
};
/*!

29
lib/utils.js Normal file
View File

@ -0,0 +1,29 @@
exports.createPromiseCallback = createPromiseCallback;
var Promise = global.Promise = require('bluebird');
function createPromiseCallback() {
var cb;
if (!global.Promise) {
cb = function() {};
cb.promise = {};
Object.defineProperty(cb.promise, 'then', { get: throwPromiseNotDefined });
Object.defineProperty(cb.promise, 'catch', { get: throwPromiseNotDefined });
return cb;
}
var promise = new Promise(function(resolve, reject) {
cb = function(err, data) {
if (err) return reject(err);
return resolve(data);
};
});
cb.promise = promise;
return cb;
}
function throwPromiseNotDefined() {
throw new Error(
'Your Node runtime does support ES6 Promises. ' +
'Set "global.Promise" to your preferred implementation of promises.');
}

View File

@ -56,6 +56,7 @@
"loopback-datasource-juggler": "^2.19.0"
},
"devDependencies": {
"bluebird": "^2.9.9",
"browserify": "^10.0.0",
"chai": "^2.1.1",
"cookie-parser": "^1.3.4",

View File

@ -234,6 +234,44 @@ describe('User', function() {
});
});
it('honors default `createAccessToken` implementation', function(done) {
User.login(validCredentialsWithTTL, function(err, accessToken) {
assert(accessToken.userId);
assert(accessToken.id);
User.findById(accessToken.userId, function(err, user) {
user.createAccessToken(120, function(err, accessToken) {
assert(accessToken.userId);
assert(accessToken.id);
assert.equal(accessToken.ttl, 120);
assert.equal(accessToken.id.length, 64);
done();
});
});
});
});
it('honors default `createAccessToken` implementation - promise variant', function(done) {
User.login(validCredentialsWithTTL, function(err, accessToken) {
assert(accessToken.userId);
assert(accessToken.id);
User.findById(accessToken.userId, function(err, user) {
user.createAccessToken(120)
.then(function(accessToken) {
assert(accessToken.userId);
assert(accessToken.id);
assert.equal(accessToken.ttl, 120);
assert.equal(accessToken.id.length, 64);
done();
})
.catch(function(err) {
done(err);
});
});
});
});
it('Login a user using a custom createAccessToken', function(done) {
var createToken = User.prototype.createAccessToken; // Save the original method
// Override createAccessToken
@ -300,6 +338,19 @@ describe('User', function() {
});
});
it('Login should only allow correct credentials - promise variant', function(done) {
User.login(invalidCredentials)
.then(function(accessToken) {
assert(!accessToken);
done();
})
.catch(function(err) {
assert(err);
assert.equal(err.code, 'LOGIN_FAILED');
done();
});
});
it('Login a user providing incomplete credentials', function(done) {
User.login(incompleteCredentials, function(err, accessToken) {
assert(err);
@ -308,6 +359,19 @@ describe('User', function() {
});
});
it('Login a user providing incomplete credentials - promise variant', function(done) {
User.login(incompleteCredentials)
.then(function(accessToken) {
assert(!accessToken);
done();
})
.catch(function(err) {
assert(err);
assert.equal(err.code, 'USERNAME_EMAIL_REQUIRED');
done();
});
});
it('Login a user over REST by providing credentials', function(done) {
request(app)
.post('/test-users/login')
@ -441,6 +505,20 @@ describe('User', function() {
});
});
it('Require valid and complete credentials for email verification error - promise variant', function(done) {
User.login({ email: validCredentialsEmail })
.then(function(accessToken) {
done();
})
.catch(function(err) {
// strongloop/loopback#931
// error message should be "login failed" and not "login failed as the email has not been verified"
assert(err && !/verified/.test(err.message), ('expecting "login failed" error message, received: "' + err.message + '"'));
assert.equal(err.code, 'LOGIN_FAILED');
done();
});
});
it('Login a user by without email verification', function(done) {
User.login(validCredentials, function(err, accessToken) {
assert(err);
@ -449,6 +527,18 @@ describe('User', function() {
});
});
it('Login a user by without email verification - promise variant', function(done) {
User.login(validCredentials)
.then(function(err, accessToken) {
done();
})
.catch(function(err) {
assert(err);
assert.equal(err.code, 'LOGIN_FAILED_EMAIL_NOT_VERIFIED');
done();
});
});
it('Login a user by with email verification', function(done) {
User.login(validCredentialsEmailVerified, function(err, accessToken) {
assertGoodToken(accessToken);
@ -456,6 +546,17 @@ describe('User', function() {
});
});
it('Login a user by with email verification - promise variant', function(done) {
User.login(validCredentialsEmailVerified)
.then(function(accessToken) {
assertGoodToken(accessToken);
done();
})
.catch(function(err) {
done(err);
});
});
it('Login a user over REST when email verification is required', function(done) {
request(app)
.post('/test-users/login')
@ -690,6 +791,22 @@ describe('User', function() {
}
});
it('Logout a user by providing the current accessToken id (using node) - promise variant', function(done) {
login(logout);
function login(fn) {
User.login({email: 'foo@bar.com', password: 'bar'}, fn);
}
function logout(err, accessToken) {
User.logout(accessToken.id)
.then(function() {
verify(accessToken.id, done);
})
.catch(done(err));
}
});
it('Logout a user by providing the current accessToken id (over rest)', function(done) {
login(logout);
function login(fn) {
@ -745,6 +862,18 @@ describe('User', function() {
});
});
it('Determine if the password matches the stored password - promise variant', function(done) {
var u = new User({username: 'foo', password: 'bar'});
u.hasPassword('bar')
.then(function(isMatch) {
assert(isMatch, 'password doesnt match');
done();
})
.catch(function(err) {
done(err);
});
});
it('should match a password when saved', function(done) {
var u = new User({username: 'a', password: 'b', email: 'z@z.net'});
@ -821,6 +950,47 @@ describe('User', function() {
});
});
it('Verify a user\'s email address - promise variant', 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)
.then(function(result) {
console.log('here in then function');
assert(result.email);
assert(result.email.response);
assert(result.token);
var msg = result.email.response.toString('utf-8');
assert(~msg.indexOf('/api/test-users/confirm'));
assert(~msg.indexOf('To: bar@bat.com'));
done();
})
.catch(function(err) {
done(err);
});
});
request(app)
.post('/test-users')
.send({email: 'bar@bat.com', password: 'bar'})
.expect('Content-Type', /json/)
.expect(200)
.end(function(err, res) {
if (err) {
return done(err);
}
});
});
it('Verify a user\'s email address with custom header', function(done) {
User.afterRemote('create', function(ctx, user, next) {
assert(user, 'afterRemote should include result');
@ -1067,6 +1237,18 @@ describe('User', function() {
});
});
it('Requires email address to reset password - promise variant', function(done) {
User.resetPassword({ })
.then(function() {
throw new Error('Error should NOT be thrown');
})
.catch(function(err) {
assert(err);
assert.equal(err.code, 'EMAIL_REQUIRED');
done();
});
});
it('Creates a temp accessToken to allow a user to change password', function(done) {
var calledBack = false;