Promisify User model
This commit is contained in:
parent
9422175510
commit
dc987a59a9
|
@ -3,6 +3,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var loopback = require('../../lib/loopback');
|
var loopback = require('../../lib/loopback');
|
||||||
|
var utils = require('../../lib/utils');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var SALT_WORK_FACTOR = 10;
|
var SALT_WORK_FACTOR = 10;
|
||||||
var crypto = require('crypto');
|
var crypto = require('crypto');
|
||||||
|
@ -80,6 +81,9 @@ module.exports = function(User) {
|
||||||
cb = options;
|
cb = options;
|
||||||
options = undefined;
|
options = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cb = cb || utils.createPromiseCallback();
|
||||||
|
|
||||||
if (typeof ttl === 'object' && !options) {
|
if (typeof ttl === 'object' && !options) {
|
||||||
// createAccessToken(options, cb)
|
// createAccessToken(options, cb)
|
||||||
options = ttl;
|
options = ttl;
|
||||||
|
@ -91,6 +95,7 @@ module.exports = function(User) {
|
||||||
this.accessTokens.create({
|
this.accessTokens.create({
|
||||||
ttl: ttl
|
ttl: ttl
|
||||||
}, cb);
|
}, cb);
|
||||||
|
return cb.promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
function splitPrincipal(name, realmDelimiter) {
|
function splitPrincipal(name, realmDelimiter) {
|
||||||
|
@ -168,6 +173,8 @@ module.exports = function(User) {
|
||||||
include = undefined;
|
include = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn = fn || utils.createPromiseCallback();
|
||||||
|
|
||||||
include = (include || '');
|
include = (include || '');
|
||||||
if (Array.isArray(include)) {
|
if (Array.isArray(include)) {
|
||||||
include = include.map(function(val) {
|
include = include.map(function(val) {
|
||||||
|
@ -191,13 +198,15 @@ module.exports = function(User) {
|
||||||
var err1 = new Error('realm is required');
|
var err1 = new Error('realm is required');
|
||||||
err1.statusCode = 400;
|
err1.statusCode = 400;
|
||||||
err1.code = 'REALM_REQUIRED';
|
err1.code = 'REALM_REQUIRED';
|
||||||
return fn(err1);
|
fn(err1);
|
||||||
|
return fn.promise;
|
||||||
}
|
}
|
||||||
if (!query.email && !query.username) {
|
if (!query.email && !query.username) {
|
||||||
var err2 = new Error('username or email is required');
|
var err2 = new Error('username or email is required');
|
||||||
err2.statusCode = 400;
|
err2.statusCode = 400;
|
||||||
err2.code = 'USERNAME_EMAIL_REQUIRED';
|
err2.code = 'USERNAME_EMAIL_REQUIRED';
|
||||||
return fn(err2);
|
fn(err2);
|
||||||
|
return fn.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.findOne({where: query}, function(err, user) {
|
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 = new Error('login failed as the email has not been verified');
|
||||||
err.statusCode = 401;
|
err.statusCode = 401;
|
||||||
err.code = 'LOGIN_FAILED_EMAIL_NOT_VERIFIED';
|
err.code = 'LOGIN_FAILED_EMAIL_NOT_VERIFIED';
|
||||||
return fn(err);
|
fn(err);
|
||||||
} else {
|
} else {
|
||||||
if (user.createAccessToken.length === 2) {
|
if (user.createAccessToken.length === 2) {
|
||||||
user.createAccessToken(credentials.ttl, tokenHandler);
|
user.createAccessToken(credentials.ttl, tokenHandler);
|
||||||
|
@ -252,6 +261,7 @@ module.exports = function(User) {
|
||||||
fn(defaultError);
|
fn(defaultError);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return fn.promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -269,6 +279,7 @@ module.exports = function(User) {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
User.logout = function(tokenId, fn) {
|
User.logout = function(tokenId, fn) {
|
||||||
|
fn = fn || utils.createPromiseCallback();
|
||||||
this.relations.accessTokens.modelTo.findById(tokenId, function(err, accessToken) {
|
this.relations.accessTokens.modelTo.findById(tokenId, function(err, accessToken) {
|
||||||
if (err) {
|
if (err) {
|
||||||
fn(err);
|
fn(err);
|
||||||
|
@ -278,6 +289,7 @@ module.exports = function(User) {
|
||||||
fn(new Error('could not find accessToken'));
|
fn(new Error('could not find accessToken'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return fn.promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -288,6 +300,7 @@ module.exports = function(User) {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
User.prototype.hasPassword = function(plain, fn) {
|
User.prototype.hasPassword = function(plain, fn) {
|
||||||
|
fn = fn || utils.createPromiseCallback();
|
||||||
if (this.password && plain) {
|
if (this.password && plain) {
|
||||||
bcrypt.compare(plain, this.password, function(err, isMatch) {
|
bcrypt.compare(plain, this.password, function(err, isMatch) {
|
||||||
if (err) return fn(err);
|
if (err) return fn(err);
|
||||||
|
@ -296,6 +309,7 @@ module.exports = function(User) {
|
||||||
} else {
|
} else {
|
||||||
fn(null, false);
|
fn(null, false);
|
||||||
}
|
}
|
||||||
|
return fn.promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -332,6 +346,8 @@ module.exports = function(User) {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
User.prototype.verify = function(options, fn) {
|
User.prototype.verify = function(options, fn) {
|
||||||
|
fn = fn || utils.createPromiseCallback();
|
||||||
|
|
||||||
var user = this;
|
var user = this;
|
||||||
var userModel = this.constructor;
|
var userModel = this.constructor;
|
||||||
var registry = userModel.registry;
|
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
|
* @param {Error} err
|
||||||
*/
|
*/
|
||||||
User.confirm = function(uid, token, redirect, fn) {
|
User.confirm = function(uid, token, redirect, fn) {
|
||||||
|
fn = fn || utils.createPromiseCallback();
|
||||||
this.findById(uid, function(err, user) {
|
this.findById(uid, function(err, user) {
|
||||||
if (err) {
|
if (err) {
|
||||||
fn(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) {
|
User.resetPassword = function(options, cb) {
|
||||||
|
cb = cb || utils.createPromiseCallback();
|
||||||
var UserModel = this;
|
var UserModel = this;
|
||||||
var ttl = UserModel.settings.resetPasswordTokenTTL || DEFAULT_RESET_PW_TTL;
|
var ttl = UserModel.settings.resetPasswordTokenTTL || DEFAULT_RESET_PW_TTL;
|
||||||
|
|
||||||
|
@ -510,6 +530,7 @@ module.exports = function(User) {
|
||||||
err.code = 'EMAIL_REQUIRED';
|
err.code = 'EMAIL_REQUIRED';
|
||||||
cb(err);
|
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"
|
"loopback-datasource-juggler": "^2.19.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"bluebird": "^2.9.9",
|
||||||
"browserify": "^10.0.0",
|
"browserify": "^10.0.0",
|
||||||
"chai": "^2.1.1",
|
"chai": "^2.1.1",
|
||||||
"cookie-parser": "^1.3.4",
|
"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) {
|
it('Login a user using a custom createAccessToken', function(done) {
|
||||||
var createToken = User.prototype.createAccessToken; // Save the original method
|
var createToken = User.prototype.createAccessToken; // Save the original method
|
||||||
// Override createAccessToken
|
// 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) {
|
it('Login a user providing incomplete credentials', function(done) {
|
||||||
User.login(incompleteCredentials, function(err, accessToken) {
|
User.login(incompleteCredentials, function(err, accessToken) {
|
||||||
assert(err);
|
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) {
|
it('Login a user over REST by providing credentials', function(done) {
|
||||||
request(app)
|
request(app)
|
||||||
.post('/test-users/login')
|
.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) {
|
it('Login a user by without email verification', function(done) {
|
||||||
User.login(validCredentials, function(err, accessToken) {
|
User.login(validCredentials, function(err, accessToken) {
|
||||||
assert(err);
|
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) {
|
it('Login a user by with email verification', function(done) {
|
||||||
User.login(validCredentialsEmailVerified, function(err, accessToken) {
|
User.login(validCredentialsEmailVerified, function(err, accessToken) {
|
||||||
assertGoodToken(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) {
|
it('Login a user over REST when email verification is required', function(done) {
|
||||||
request(app)
|
request(app)
|
||||||
.post('/test-users/login')
|
.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) {
|
it('Logout a user by providing the current accessToken id (over rest)', function(done) {
|
||||||
login(logout);
|
login(logout);
|
||||||
function login(fn) {
|
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) {
|
it('should match a password when saved', function(done) {
|
||||||
var u = new User({username: 'a', password: 'b', email: 'z@z.net'});
|
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) {
|
it('Verify a user\'s email address with custom header', function(done) {
|
||||||
User.afterRemote('create', function(ctx, user, next) {
|
User.afterRemote('create', function(ctx, user, next) {
|
||||||
assert(user, 'afterRemote should include result');
|
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) {
|
it('Creates a temp accessToken to allow a user to change password', function(done) {
|
||||||
var calledBack = false;
|
var calledBack = false;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue