Promisify User model
This commit is contained in:
parent
9422175510
commit
dc987a59a9
|
@ -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;
|
||||
};
|
||||
|
||||
/*!
|
||||
|
|
|
@ -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.');
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue