Enable multiple user models
Allow LoopBack applications to configure multiple User models and share the same AccessToken model. To enable this feature: 1) In your custom AccessToken model: - add a new property "principalType" of type "string". - configure the relation "belongsTo user" as polymorphic, using "principalType" as the discriminator 2) In your User models: - Configure the "hasMany accessTokens" relation as polymorphic, using "principalType" as the discriminator When creating custom Role and Principal instances, set your User model's name as the value of "prinicipalType".
This commit is contained in:
parent
798ebfba81
commit
9fe084fffd
|
@ -146,6 +146,16 @@ module.exports = function(AccessToken) {
|
||||||
var userRelation = AccessToken.relations.user; // may not be set up
|
var userRelation = AccessToken.relations.user; // may not be set up
|
||||||
var User = userRelation && userRelation.modelTo;
|
var User = userRelation && userRelation.modelTo;
|
||||||
|
|
||||||
|
// redefine user model if accessToken's principalType is available
|
||||||
|
if (this.principalType) {
|
||||||
|
User = AccessToken.registry.findModel(this.principalType);
|
||||||
|
if (!User) {
|
||||||
|
process.nextTick(function() {
|
||||||
|
return cb(null, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var now = Date.now();
|
var now = Date.now();
|
||||||
var created = this.created.getTime();
|
var created = this.created.getTime();
|
||||||
var elapsedSeconds = (now - created) / 1000;
|
var elapsedSeconds = (now - created) / 1000;
|
||||||
|
@ -157,14 +167,18 @@ module.exports = function(AccessToken) {
|
||||||
elapsedSeconds < secondsToLive;
|
elapsedSeconds < secondsToLive;
|
||||||
|
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
|
process.nextTick(function() {
|
||||||
cb(null, isValid);
|
cb(null, isValid);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.destroy(function(err) {
|
this.destroy(function(err) {
|
||||||
cb(err, isValid);
|
cb(err, isValid);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
process.nextTick(function() {
|
||||||
cb(e);
|
cb(e);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -395,6 +395,7 @@ module.exports = function(ACL) {
|
||||||
self.resolveRelatedModels();
|
self.resolveRelatedModels();
|
||||||
var roleModel = self.roleModel;
|
var roleModel = self.roleModel;
|
||||||
|
|
||||||
|
context.registry = this.registry;
|
||||||
if (!(context instanceof AccessContext)) {
|
if (!(context instanceof AccessContext)) {
|
||||||
context = new AccessContext(context);
|
context = new AccessContext(context);
|
||||||
}
|
}
|
||||||
|
@ -480,6 +481,7 @@ module.exports = function(ACL) {
|
||||||
assert(token, 'Access token is required');
|
assert(token, 'Access token is required');
|
||||||
if (!callback) callback = utils.createPromiseCallback();
|
if (!callback) callback = utils.createPromiseCallback();
|
||||||
var context = new AccessContext({
|
var context = new AccessContext({
|
||||||
|
registry: this.registry,
|
||||||
accessToken: token,
|
accessToken: token,
|
||||||
model: model,
|
model: model,
|
||||||
property: method,
|
property: method,
|
||||||
|
@ -514,6 +516,7 @@ module.exports = function(ACL) {
|
||||||
cb = cb || utils.createPromiseCallback();
|
cb = cb || utils.createPromiseCallback();
|
||||||
type = type || ACL.ROLE;
|
type = type || ACL.ROLE;
|
||||||
this.resolveRelatedModels();
|
this.resolveRelatedModels();
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case ACL.ROLE:
|
case ACL.ROLE:
|
||||||
this.roleModel.findOne({where: {or: [{name: id}, {id: id}]}}, cb);
|
this.roleModel.findOne({where: {or: [{name: id}, {id: id}]}}, cb);
|
||||||
|
@ -527,12 +530,21 @@ module.exports = function(ACL) {
|
||||||
{where: {or: [{name: id}, {email: id}, {id: id}]}}, cb);
|
{where: {or: [{name: id}, {email: id}, {id: id}]}}, cb);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
// try resolving a user model that matches principalType
|
||||||
|
var userModel = this.registry.findModel(type);
|
||||||
|
if (userModel) {
|
||||||
|
userModel.findOne(
|
||||||
|
{where: {or: [{username: id}, {email: id}, {id: id}]}},
|
||||||
|
cb);
|
||||||
|
} else {
|
||||||
process.nextTick(function() {
|
process.nextTick(function() {
|
||||||
var err = new Error(g.f('Invalid principal type: %s', type));
|
var err = new Error(g.f('Invalid principal type: %s', type));
|
||||||
err.statusCode = 400;
|
err.statusCode = 400;
|
||||||
|
err.code = 'INVALID_PRINCIPAL_TYPE';
|
||||||
cb(err);
|
cb(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return cb.promise;
|
return cb.promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -63,9 +63,17 @@ module.exports = function(RoleMapping) {
|
||||||
RoleMapping.prototype.user = function(callback) {
|
RoleMapping.prototype.user = function(callback) {
|
||||||
callback = callback || utils.createPromiseCallback();
|
callback = callback || utils.createPromiseCallback();
|
||||||
this.constructor.resolveRelatedModels();
|
this.constructor.resolveRelatedModels();
|
||||||
|
var userModel;
|
||||||
|
|
||||||
if (this.principalType === RoleMapping.USER) {
|
if (this.principalType === RoleMapping.USER) {
|
||||||
var userModel = this.constructor.userModel;
|
userModel = this.constructor.userModel;
|
||||||
|
userModel.findById(this.principalId, callback);
|
||||||
|
return callback.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
// try resolving a user model that matches principalType
|
||||||
|
userModel = this.constructor.registry.findModel(this.principalType);
|
||||||
|
if (userModel) {
|
||||||
userModel.findById(this.principalId, callback);
|
userModel.findById(this.principalId, callback);
|
||||||
} else {
|
} else {
|
||||||
process.nextTick(function() {
|
process.nextTick(function() {
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
},
|
},
|
||||||
"principalType": {
|
"principalType": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The principal type, such as user, application, or role"
|
"description": "The principal type, such as USER, APPLICATION, ROLE, or user model name in case of multiple user models"
|
||||||
},
|
},
|
||||||
"principalId": {
|
"principalId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
|
@ -9,9 +9,9 @@ var debug = require('debug')('loopback:security:role');
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var utils = require('../../lib/utils');
|
var utils = require('../../lib/utils');
|
||||||
|
var ctx = require('../../lib/access-context');
|
||||||
var AccessContext = require('../../lib/access-context').AccessContext;
|
var AccessContext = ctx.AccessContext;
|
||||||
|
var Principal = ctx.Principal;
|
||||||
var RoleMapping = loopback.RoleMapping;
|
var RoleMapping = loopback.RoleMapping;
|
||||||
|
|
||||||
assert(RoleMapping, 'RoleMapping model must be defined before Role model');
|
assert(RoleMapping, 'RoleMapping model must be defined before Role model');
|
||||||
|
@ -70,7 +70,8 @@ module.exports = function(Role) {
|
||||||
callback = utils.createPromiseCallback();
|
callback = utils.createPromiseCallback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!query) query = {};
|
query = query || {};
|
||||||
|
query.where = query.where || {};
|
||||||
|
|
||||||
roleModel.resolveRelatedModels();
|
roleModel.resolveRelatedModels();
|
||||||
var relsToModels = {
|
var relsToModels = {
|
||||||
|
@ -86,8 +87,29 @@ module.exports = function(Role) {
|
||||||
roles: ACL.ROLE,
|
roles: ACL.ROLE,
|
||||||
};
|
};
|
||||||
|
|
||||||
var model = relsToModels[rel];
|
var principalModel = relsToModels[rel];
|
||||||
listByPrincipalType(this, model, relsToTypes[rel], query, callback);
|
var principalType = relsToTypes[rel];
|
||||||
|
|
||||||
|
// redefine user model and user type if user principalType is custom (available and not "USER")
|
||||||
|
var isCustomUserPrincipalType = rel === 'users' &&
|
||||||
|
query.where.principalType &&
|
||||||
|
query.where.principalType !== RoleMapping.USER;
|
||||||
|
|
||||||
|
if (isCustomUserPrincipalType) {
|
||||||
|
var registry = this.constructor.registry;
|
||||||
|
principalModel = registry.findModel(query.where.principalType);
|
||||||
|
principalType = query.where.principalType;
|
||||||
|
}
|
||||||
|
// make sure we don't keep principalType in userModel query
|
||||||
|
delete query.where.principalType;
|
||||||
|
|
||||||
|
if (principalModel) {
|
||||||
|
listByPrincipalType(this, principalModel, principalType, query, callback);
|
||||||
|
} else {
|
||||||
|
process.nextTick(function() {
|
||||||
|
callback(null, []);
|
||||||
|
});
|
||||||
|
}
|
||||||
return callback.promise;
|
return callback.promise;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -102,10 +124,11 @@ module.exports = function(Role) {
|
||||||
* @param {Function} [callback] callback function called with `(err, models)` arguments.
|
* @param {Function} [callback] callback function called with `(err, models)` arguments.
|
||||||
*/
|
*/
|
||||||
function listByPrincipalType(context, model, principalType, query, callback) {
|
function listByPrincipalType(context, model, principalType, query, callback) {
|
||||||
if (callback === undefined) {
|
if (callback === undefined && typeof query === 'function') {
|
||||||
callback = query;
|
callback = query;
|
||||||
query = {};
|
query = {};
|
||||||
}
|
}
|
||||||
|
query = query || {};
|
||||||
|
|
||||||
roleModel.roleMappingModel.find({
|
roleModel.roleMappingModel.find({
|
||||||
where: {roleId: context.id, principalType: principalType},
|
where: {roleId: context.id, principalType: principalType},
|
||||||
|
@ -303,6 +326,7 @@ module.exports = function(Role) {
|
||||||
* @promise
|
* @promise
|
||||||
*/
|
*/
|
||||||
Role.isInRole = function(role, context, callback) {
|
Role.isInRole = function(role, context, callback) {
|
||||||
|
context.registry = this.registry;
|
||||||
if (!(context instanceof AccessContext)) {
|
if (!(context instanceof AccessContext)) {
|
||||||
context = new AccessContext(context);
|
context = new AccessContext(context);
|
||||||
}
|
}
|
||||||
|
@ -421,9 +445,9 @@ module.exports = function(Role) {
|
||||||
callback = utils.createPromiseCallback();
|
callback = utils.createPromiseCallback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options) options = {};
|
if (!options) options = {};
|
||||||
|
|
||||||
|
context.registry = this.registry;
|
||||||
if (!(context instanceof AccessContext)) {
|
if (!(context instanceof AccessContext)) {
|
||||||
context = new AccessContext(context);
|
context = new AccessContext(context);
|
||||||
}
|
}
|
||||||
|
|
|
@ -683,13 +683,18 @@ module.exports = function(User) {
|
||||||
return process.nextTick(cb);
|
return process.nextTick(cb);
|
||||||
|
|
||||||
var AccessToken = accessTokenRelation.modelTo;
|
var AccessToken = accessTokenRelation.modelTo;
|
||||||
|
|
||||||
var query = {userId: {inq: userIds}};
|
var query = {userId: {inq: userIds}};
|
||||||
var tokenPK = AccessToken.definition.idName() || 'id';
|
var tokenPK = AccessToken.definition.idName() || 'id';
|
||||||
if (options.accessToken && tokenPK in options.accessToken) {
|
if (options.accessToken && tokenPK in options.accessToken) {
|
||||||
query[tokenPK] = {neq: options.accessToken[tokenPK]};
|
query[tokenPK] = {neq: options.accessToken[tokenPK]};
|
||||||
}
|
}
|
||||||
|
// add principalType in AccessToken.query if using polymorphic relations
|
||||||
|
// between AccessToken and User
|
||||||
|
var relatedUser = AccessToken.relations.user;
|
||||||
|
var isRelationPolymorphic = relatedUser.polymorphic && !relatedUser.modelTo;
|
||||||
|
if (isRelationPolymorphic) {
|
||||||
|
query.principalType = this.modelName;
|
||||||
|
}
|
||||||
AccessToken.deleteAll(query, options, cb);
|
AccessToken.deleteAll(query, options, cb);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -32,9 +32,12 @@ function AccessContext(context) {
|
||||||
}
|
}
|
||||||
context = context || {};
|
context = context || {};
|
||||||
|
|
||||||
|
assert(context.registry,
|
||||||
|
'Application registry is mandatory in AccessContext but missing in provided context');
|
||||||
|
this.registry = context.registry;
|
||||||
this.principals = context.principals || [];
|
this.principals = context.principals || [];
|
||||||
var model = context.model;
|
var model = context.model;
|
||||||
model = ('string' === typeof model) ? loopback.getModel(model) : model;
|
model = ('string' === typeof model) ? this.registry.getModel(model) : model;
|
||||||
this.model = model;
|
this.model = model;
|
||||||
this.modelName = model && model.modelName;
|
this.modelName = model && model.modelName;
|
||||||
|
|
||||||
|
@ -62,6 +65,7 @@ function AccessContext(context) {
|
||||||
var principalType = context.principalType || Principal.USER;
|
var principalType = context.principalType || Principal.USER;
|
||||||
var principalId = context.principalId || undefined;
|
var principalId = context.principalId || undefined;
|
||||||
var principalName = context.principalName || undefined;
|
var principalName = context.principalName || undefined;
|
||||||
|
|
||||||
if (principalId) {
|
if (principalId) {
|
||||||
this.addPrincipal(principalType, principalId, principalName);
|
this.addPrincipal(principalType, principalId, principalName);
|
||||||
}
|
}
|
||||||
|
@ -124,11 +128,25 @@ AccessContext.prototype.addPrincipal = function(principalType, principalId, prin
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
AccessContext.prototype.getUserId = function() {
|
AccessContext.prototype.getUserId = function() {
|
||||||
|
var BaseUser = this.registry.getModel('User');
|
||||||
for (var i = 0; i < this.principals.length; i++) {
|
for (var i = 0; i < this.principals.length; i++) {
|
||||||
var p = this.principals[i];
|
var p = this.principals[i];
|
||||||
|
var isBuiltinPrincipal = p.type === Principal.APP ||
|
||||||
|
p.type === Principal.ROLE ||
|
||||||
|
p.type == Principal.SCOPE;
|
||||||
|
if (isBuiltinPrincipal) continue;
|
||||||
|
|
||||||
|
// the principalType must either be 'USER'
|
||||||
if (p.type === Principal.USER) {
|
if (p.type === Principal.USER) {
|
||||||
return p.id;
|
return p.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// or permit to resolve a valid user model
|
||||||
|
var userModel = this.registry.findModel(p.type);
|
||||||
|
if (!userModel) continue;
|
||||||
|
if (userModel.prototype instanceof BaseUser) {
|
||||||
|
return p.id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
@ -189,8 +207,9 @@ AccessContext.prototype.debug = function() {
|
||||||
* This class represents the abstract notion of a principal, which can be used
|
* This class represents the abstract notion of a principal, which can be used
|
||||||
* to represent any entity, such as an individual, a corporation, and a login id
|
* to represent any entity, such as an individual, a corporation, and a login id
|
||||||
* @param {String} type The principal type
|
* @param {String} type The principal type
|
||||||
* @param {*} id The princiapl id
|
* @param {*} id The principal id
|
||||||
* @param {String} [name] The principal name
|
* @param {String} [name] The principal name
|
||||||
|
* @param {String} modelName The principal model name
|
||||||
* @returns {Principal}
|
* @returns {Principal}
|
||||||
* @class
|
* @class
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,444 @@
|
||||||
|
// Copyright IBM Corp. 2013,2016. All Rights Reserved.
|
||||||
|
// Node module: loopback
|
||||||
|
// This file is licensed under the MIT License.
|
||||||
|
// License text available at https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
var expect = require('./helpers/expect');
|
||||||
|
var request = require('supertest');
|
||||||
|
var loopback = require('../');
|
||||||
|
var ctx = require('../lib/access-context');
|
||||||
|
var AccessContext = ctx.AccessContext;
|
||||||
|
var Principal = ctx.Principal;
|
||||||
|
var Promise = require('bluebird');
|
||||||
|
|
||||||
|
describe('Multiple users with custom principalType', function() {
|
||||||
|
this.timeout(10000);
|
||||||
|
|
||||||
|
var commonCredentials = {email: 'foo@bar.com', password: 'bar'};
|
||||||
|
var app, OneUser, AnotherUser, AccessToken, Role,
|
||||||
|
userFromOneModel, userFromAnotherModel, userRole, userOneBaseContext;
|
||||||
|
|
||||||
|
beforeEach(function setupAppAndModels() {
|
||||||
|
// create a local app object that does not share state with other tests
|
||||||
|
app = loopback({localRegistry: true, loadBuiltinModels: true});
|
||||||
|
app.dataSource('db', {connector: 'memory'});
|
||||||
|
|
||||||
|
var userModelOptions = {
|
||||||
|
base: 'User',
|
||||||
|
// forceId is set to false for the purpose of updating the same affected user within the
|
||||||
|
// `Email Update` test cases.
|
||||||
|
forceId: false,
|
||||||
|
// Speed up the password hashing algorithm for tests
|
||||||
|
saltWorkFactor: 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
// create and attach 2 User-based models
|
||||||
|
OneUser = createUserModel(app, 'OneUser', userModelOptions);
|
||||||
|
AnotherUser = createUserModel(app, 'AnotherUser', userModelOptions);
|
||||||
|
|
||||||
|
AccessToken = app.registry.getModel('AccessToken');
|
||||||
|
app.model(AccessToken, {dataSource: 'db'});
|
||||||
|
|
||||||
|
Role = app.registry.getModel('Role');
|
||||||
|
app.model(Role, {dataSource: 'db'});
|
||||||
|
|
||||||
|
// Update AccessToken and Users to bind them through polymorphic relations
|
||||||
|
AccessToken.belongsTo('user', {idName: 'id', polymorphic: {idType: 'string',
|
||||||
|
foreignKey: 'userId', discriminator: 'principalType'}});
|
||||||
|
OneUser.hasMany('accessTokens', {polymorphic: {foreignKey: 'userId',
|
||||||
|
discriminator: 'principalType'}});
|
||||||
|
AnotherUser.hasMany('accessTokens', {polymorphic: {foreignKey: 'userId',
|
||||||
|
discriminator: 'principalType'}});
|
||||||
|
|
||||||
|
app.enableAuth({dataSource: 'db'});
|
||||||
|
app.use(loopback.token({model: AccessToken}));
|
||||||
|
|
||||||
|
// create one user per user model to use them throughout the tests
|
||||||
|
return Promise.all([
|
||||||
|
OneUser.create(commonCredentials),
|
||||||
|
AnotherUser.create(commonCredentials),
|
||||||
|
Role.create({name: 'userRole'}),
|
||||||
|
])
|
||||||
|
.spread(function(u1, u2, r) {
|
||||||
|
userFromOneModel = u1;
|
||||||
|
userFromAnotherModel = u2;
|
||||||
|
userRole = r;
|
||||||
|
userOneBaseContext = {
|
||||||
|
principalType: OneUser.modelName,
|
||||||
|
principalId: userFromOneModel.id,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('User.login', function() {
|
||||||
|
it('works for one user model and valid credentials', function() {
|
||||||
|
return OneUser.login(commonCredentials)
|
||||||
|
.then(function(accessToken) {
|
||||||
|
assertGoodToken(accessToken, userFromOneModel);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works for a second user model and valid credentials', function() {
|
||||||
|
return AnotherUser.login(commonCredentials)
|
||||||
|
.then(function(accessToken) {
|
||||||
|
assertGoodToken(accessToken, userFromAnotherModel);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails when credentials are not correct', function() {
|
||||||
|
return OneUser.login({email: 'foo@bar.com', password: 'invalid'})
|
||||||
|
.then(
|
||||||
|
function onSuccess() {
|
||||||
|
throw new Error('OneUser.login() should have failed');
|
||||||
|
},
|
||||||
|
function onError(err) {
|
||||||
|
expect(err).to.have.property('code', 'LOGIN_FAILED');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function assertGoodToken(accessToken, user) {
|
||||||
|
if (accessToken instanceof AccessToken) {
|
||||||
|
accessToken = accessToken.toJSON();
|
||||||
|
}
|
||||||
|
expect(accessToken.id, 'token id').to.have.lengthOf(64);
|
||||||
|
expect(accessToken).to.have.property('userId', user.id);
|
||||||
|
expect(accessToken).to.have.property('principalType', user.constructor.definition.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('User.logout', function() {
|
||||||
|
it('logs out a user from user model 1 without logging out user from model 2',
|
||||||
|
function() {
|
||||||
|
var tokenOfOneUser;
|
||||||
|
return Promise.all([
|
||||||
|
OneUser.login(commonCredentials),
|
||||||
|
AnotherUser.login(commonCredentials),
|
||||||
|
])
|
||||||
|
.spread(function(t1, t2) {
|
||||||
|
tokenOfOneUser = t1;
|
||||||
|
return OneUser.logout(tokenOfOneUser.id);
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
return AccessToken.find({});
|
||||||
|
})
|
||||||
|
.then(function(allTokens) {
|
||||||
|
var data = allTokens.map(function(token) {
|
||||||
|
return {userId: token.userId, principalType: token.principalType};
|
||||||
|
});
|
||||||
|
expect(data).to.eql([
|
||||||
|
// no token for userFromAnotherModel
|
||||||
|
{userId: userFromAnotherModel.id, principalType: 'AnotherUser'},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Password Reset', function() {
|
||||||
|
describe('User.resetPassword(options)', function() {
|
||||||
|
var options = {
|
||||||
|
email: 'foo@bar.com',
|
||||||
|
redirect: 'http://foobar.com/reset-password',
|
||||||
|
};
|
||||||
|
|
||||||
|
it('creates a temp accessToken to allow a user to change password',
|
||||||
|
function() {
|
||||||
|
return Promise.all([
|
||||||
|
OneUser.resetPassword({email: options.email}),
|
||||||
|
waitForResetRequestAndVerify,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
function waitForResetRequestAndVerify() {
|
||||||
|
return waitForEvent(OneUser, 'resetPasswordRequest')
|
||||||
|
.then(function(info) {
|
||||||
|
assertGoodToken(info.accessToken, userFromOneModel);
|
||||||
|
return info.accessToken.user.getAsync();
|
||||||
|
})
|
||||||
|
.then(function(user) {
|
||||||
|
expect(user).to.have.property('id', userFromOneModel.id);
|
||||||
|
expect(user).to.have.property('email', userFromOneModel.email);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('AccessToken (session) invalidation when changing email', function() {
|
||||||
|
var anotherUserFromOneModel;
|
||||||
|
|
||||||
|
it('impact only the related user', function() {
|
||||||
|
return OneUser.create({email: 'original@example.com', password: 'bar'})
|
||||||
|
.then(function(u) {
|
||||||
|
anotherUserFromOneModel = u;
|
||||||
|
return Promise.all([
|
||||||
|
OneUser.login({email: 'original@example.com', password: 'bar'}),
|
||||||
|
OneUser.login(commonCredentials),
|
||||||
|
AnotherUser.login(commonCredentials),
|
||||||
|
]);
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
return anotherUserFromOneModel.updateAttribute('email', 'updated@example.com');
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
// we need to sort on principalType to ensure stability in results' order
|
||||||
|
return AccessToken.find({'order': 'principalType ASC'});
|
||||||
|
})
|
||||||
|
.then(function(allTokens) {
|
||||||
|
var data = allTokens.map(function(token) {
|
||||||
|
return {userId: token.userId, principalType: token.principalType};
|
||||||
|
});
|
||||||
|
expect(data).to.eql([
|
||||||
|
// no token for anotherUserFromOneModel
|
||||||
|
{userId: userFromAnotherModel.id, principalType: 'AnotherUser'},
|
||||||
|
{userId: userFromOneModel.id, principalType: 'OneUser'},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('AccessContext', function() {
|
||||||
|
var ThirdUser, userFromThirdModel, accessContext;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
accessContext = new AccessContext({registry: OneUser.registry});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getUserId()', function() {
|
||||||
|
it('returns userId although principals contain non USER principals',
|
||||||
|
function() {
|
||||||
|
return Promise.try(function() {
|
||||||
|
addToAccessContext([
|
||||||
|
{type: Principal.ROLE},
|
||||||
|
{type: Principal.APP},
|
||||||
|
{type: Principal.SCOPE},
|
||||||
|
{type: OneUser.modelName, id: userFromOneModel.id},
|
||||||
|
]);
|
||||||
|
var userId = accessContext.getUserId();
|
||||||
|
expect(userId).to.equal(userFromOneModel.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns userId although principals contain invalid principals',
|
||||||
|
function() {
|
||||||
|
return Promise.try(function() {
|
||||||
|
addToAccessContext([
|
||||||
|
{type: 'AccessToken'},
|
||||||
|
{type: 'invalidModelName'},
|
||||||
|
{type: OneUser.modelName, id: userFromOneModel.id},
|
||||||
|
]);
|
||||||
|
var userId = accessContext.getUserId();
|
||||||
|
expect(userId).to.equal(userFromOneModel.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports any level of built-in User model inheritance',
|
||||||
|
function() {
|
||||||
|
ThirdUser = createUserModel(app, 'ThirdUser', {base: 'OneUser'});
|
||||||
|
return ThirdUser.create(commonCredentials)
|
||||||
|
.then(function(userFromThirdModel) {
|
||||||
|
accessContext.addPrincipal(ThirdUser.modelName, userFromThirdModel.id);
|
||||||
|
var userId = accessContext.getUserId();
|
||||||
|
expect(userId).to.equal(userFromThirdModel.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// helper
|
||||||
|
function addToAccessContext(list) {
|
||||||
|
list.forEach(function(principal) {
|
||||||
|
expect(principal).to.exist();
|
||||||
|
accessContext.addPrincipal(principal.type, principal.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('role model', function() {
|
||||||
|
this.timeout(10000);
|
||||||
|
|
||||||
|
var RoleMapping, ACL, user;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
ACL = app.registry.getModel('ACL');
|
||||||
|
app.model(ACL, {dataSource: 'db'});
|
||||||
|
|
||||||
|
RoleMapping = app.registry.getModel('RoleMapping');
|
||||||
|
app.model(RoleMapping, {dataSource: 'db'});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('role.users()', function() {
|
||||||
|
it('returns users when using custom user principalType', function() {
|
||||||
|
return userRole.principals.create(
|
||||||
|
{principalType: OneUser.modelName, principalId: userFromOneModel.id})
|
||||||
|
.then(function() {
|
||||||
|
return userRole.users({where: {principalType: OneUser.modelName}});
|
||||||
|
})
|
||||||
|
.then(getIds)
|
||||||
|
.then(function(userIds) {
|
||||||
|
expect(userIds).to.eql([userFromOneModel.id]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns empty array when using invalid principalType', function() {
|
||||||
|
return userRole.principals.create(
|
||||||
|
{principalType: 'invalidModelName', principalId: userFromOneModel.id})
|
||||||
|
.then(function() {
|
||||||
|
return userRole.users({where: {principalType: 'invalidModelName'}});
|
||||||
|
})
|
||||||
|
.then(function(users) {
|
||||||
|
expect(users).to.be.empty();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('principal.user()', function() {
|
||||||
|
it('returns the correct user instance', function() {
|
||||||
|
return userRole.principals.create(
|
||||||
|
{principalType: OneUser.modelName, principalId: userFromOneModel.id})
|
||||||
|
.then(function(principal) {
|
||||||
|
return principal.user();
|
||||||
|
})
|
||||||
|
.then(function(user) {
|
||||||
|
expect(user).to.have.property('id', userFromOneModel.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null when created with invalid principalType', function() {
|
||||||
|
return userRole.principals.create(
|
||||||
|
{principalType: 'invalidModelName', principalId: userFromOneModel.id})
|
||||||
|
.then(function(principal) {
|
||||||
|
return principal.user();
|
||||||
|
})
|
||||||
|
.then(function(user) {
|
||||||
|
expect(user).to.not.exist();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isInRole() & getRole()', function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
return userRole.principals.create({principalType: OneUser.modelName,
|
||||||
|
principalId: userFromOneModel.id});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports isInRole()', function() {
|
||||||
|
return Role.isInRole('userRole', userOneBaseContext)
|
||||||
|
.then(function(isInRole) {
|
||||||
|
expect(isInRole).to.be.true();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports getRoles()', function() {
|
||||||
|
return Role.getRoles(
|
||||||
|
userOneBaseContext)
|
||||||
|
.then(function(roles) {
|
||||||
|
expect(roles).to.eql([
|
||||||
|
Role.AUTHENTICATED,
|
||||||
|
Role.EVERYONE,
|
||||||
|
userRole.id,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('built-in role resolver', function() {
|
||||||
|
it('supports AUTHENTICATED', function() {
|
||||||
|
return Role.isInRole(Role.AUTHENTICATED, userOneBaseContext)
|
||||||
|
.then(function(isInRole) {
|
||||||
|
expect(isInRole).to.be.true();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports UNAUTHENTICATED', function() {
|
||||||
|
return Role.isInRole(Role.UNAUTHENTICATED, userOneBaseContext)
|
||||||
|
.then(function(isInRole) {
|
||||||
|
expect(isInRole).to.be.false();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports OWNER', function() {
|
||||||
|
var Album = app.registry.createModel('Album', {
|
||||||
|
name: String,
|
||||||
|
userId: Number,
|
||||||
|
}, {
|
||||||
|
relations: {
|
||||||
|
user: {
|
||||||
|
type: 'belongsTo',
|
||||||
|
model: 'OneUser',
|
||||||
|
foreignKey: 'userId',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
app.model(Album, {dataSource: 'db'});
|
||||||
|
|
||||||
|
return Album.create({name: 'album', userId: userFromOneModel.id})
|
||||||
|
.then(function(album) {
|
||||||
|
return Role.isInRole(
|
||||||
|
Role.OWNER,
|
||||||
|
{
|
||||||
|
principalType: OneUser.modelName,
|
||||||
|
principalId: userFromOneModel.id,
|
||||||
|
model: Album,
|
||||||
|
id: album.id,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(function(isInRole) {
|
||||||
|
expect(isInRole).to.be.true();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isMappedToRole()', function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
return userRole.principals.create(userOneBaseContext);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves user by id using custom user principalType', function() {
|
||||||
|
return ACL.resolvePrincipal(OneUser.modelName, userFromOneModel.id)
|
||||||
|
.then(function(principal) {
|
||||||
|
expect(principal.id).to.eql(userFromOneModel.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws error with code \'INVALID_PRINCIPAL_TYPE\' when principalType is incorrect',
|
||||||
|
function() {
|
||||||
|
return ACL.resolvePrincipal('incorrectPrincipalType', userFromOneModel.id)
|
||||||
|
.then(
|
||||||
|
function onSuccess() {
|
||||||
|
throw new Error('ACL.resolvePrincipal() should have failed');
|
||||||
|
},
|
||||||
|
function onError(err) {
|
||||||
|
expect(err).to.have.property('statusCode', 400);
|
||||||
|
expect(err).to.have.property('code', 'INVALID_PRINCIPAL_TYPE');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reports isMappedToRole by user.username using custom user principalType',
|
||||||
|
function() {
|
||||||
|
return ACL.isMappedToRole(OneUser.modelName, userFromOneModel.username, 'userRole')
|
||||||
|
.then(function(isMappedToRole) {
|
||||||
|
expect(isMappedToRole).to.be.true();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// helpers
|
||||||
|
function createUserModel(app, name, options) {
|
||||||
|
var model = app.registry.createModel(Object.assign({name: name}, options));
|
||||||
|
app.model(model, {dataSource: 'db'});
|
||||||
|
model.setMaxListeners(0); // allow many User.afterRemote's to be called
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
function waitForEvent(emitter, name) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
emitter.once(name, resolve);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function getIds(array) {
|
||||||
|
return array.map(function(it) { return it.id; });
|
||||||
|
};
|
||||||
|
});
|
|
@ -28,11 +28,12 @@ describe('User', function() {
|
||||||
var validMixedCaseEmailCredentials = {email: 'Foo@bar.com', password: 'bar'};
|
var validMixedCaseEmailCredentials = {email: 'Foo@bar.com', password: 'bar'};
|
||||||
var invalidCredentials = {email: 'foo1@bar.com', password: 'invalid'};
|
var invalidCredentials = {email: 'foo1@bar.com', password: 'invalid'};
|
||||||
var incompleteCredentials = {password: 'bar1'};
|
var incompleteCredentials = {password: 'bar1'};
|
||||||
|
var validCredentialsUser, validCredentialsEmailVerifiedUser;
|
||||||
|
|
||||||
// Create a local app variable to prevent clashes with the global
|
// Create a local app variable to prevent clashes with the global
|
||||||
// variable shared by all tests. While this should not be necessary if
|
// variable shared by all tests. While this should not be necessary if
|
||||||
// the tests were written correctly, it turns out that's not the case :(
|
// the tests were written correctly, it turns out that's not the case :(
|
||||||
var app;
|
var app = null;
|
||||||
|
|
||||||
beforeEach(function setupAppAndModels(done) {
|
beforeEach(function setupAppAndModels(done) {
|
||||||
// override the global app object provided by test/support.js
|
// override the global app object provided by test/support.js
|
||||||
|
@ -89,8 +90,12 @@ describe('User', function() {
|
||||||
|
|
||||||
User.create(validCredentials, function(err, user) {
|
User.create(validCredentials, function(err, user) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
validCredentialsUser = user;
|
||||||
User.create(validCredentialsEmailVerified, done);
|
User.create(validCredentialsEmailVerified, function(err, user) {
|
||||||
|
if (err) return done(err);
|
||||||
|
validCredentialsEmailVerifiedUser = user;
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -279,14 +284,15 @@ describe('User', function() {
|
||||||
|
|
||||||
it('invalidates the user\'s accessToken when the user is deleted all', function(done) {
|
it('invalidates the user\'s accessToken when the user is deleted all', function(done) {
|
||||||
var userIds = [];
|
var userIds = [];
|
||||||
var accessTokenId;
|
var users;
|
||||||
async.series([
|
async.series([
|
||||||
function(next) {
|
function(next) {
|
||||||
User.create([
|
User.create([
|
||||||
{name: 'myname', email: 'b@c.com', password: 'bar'},
|
{name: 'myname', email: 'b@c.com', password: 'bar'},
|
||||||
{name: 'myname', email: 'd@c.com', password: 'bar'},
|
{name: 'myname', email: 'd@c.com', password: 'bar'},
|
||||||
], function(err, users) {
|
], function(err, createdUsers) {
|
||||||
userIds = users.map(function(u) {
|
users = createdUsers;
|
||||||
|
userIds = createdUsers.map(function(u) {
|
||||||
return u.pk;
|
return u.pk;
|
||||||
});
|
});
|
||||||
next(err);
|
next(err);
|
||||||
|
@ -294,17 +300,15 @@ describe('User', function() {
|
||||||
},
|
},
|
||||||
function(next) {
|
function(next) {
|
||||||
User.login({email: 'b@c.com', password: 'bar'}, function(err, accessToken) {
|
User.login({email: 'b@c.com', password: 'bar'}, function(err, accessToken) {
|
||||||
accessTokenId = accessToken.userId;
|
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
assert(accessTokenId);
|
assertGoodToken(accessToken, users[0]);
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function(next) {
|
function(next) {
|
||||||
User.login({email: 'd@c.com', password: 'bar'}, function(err, accessToken) {
|
User.login({email: 'd@c.com', password: 'bar'}, function(err, accessToken) {
|
||||||
accessTokenId = accessToken.userId;
|
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
assert(accessTokenId);
|
assertGoodToken(accessToken, users[1]);
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -424,12 +428,11 @@ describe('User', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('allows login with password exactly 72 characters long', function(done) {
|
it('allows login with password exactly 72 characters long', function(done) {
|
||||||
User.create({email: 'b@c.com', password: pass72Char}, function(err) {
|
User.create({email: 'b@c.com', password: pass72Char}, function(err, user) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
User.login({email: 'b@c.com', password: pass72Char}, function(err, accessToken) {
|
User.login({email: 'b@c.com', password: pass72Char}, function(err, accessToken) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
assertGoodToken(accessToken);
|
assertGoodToken(accessToken, user);
|
||||||
assert(accessToken.id);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -502,9 +505,7 @@ describe('User', function() {
|
||||||
describe('User.login', function() {
|
describe('User.login', function() {
|
||||||
it('Login a user by providing credentials', function(done) {
|
it('Login a user by providing credentials', function(done) {
|
||||||
User.login(validCredentials, function(err, accessToken) {
|
User.login(validCredentials, function(err, accessToken) {
|
||||||
assert(accessToken.userId);
|
assertGoodToken(accessToken, validCredentialsUser);
|
||||||
assert(accessToken.id);
|
|
||||||
assert.equal(accessToken.id.length, 64);
|
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -513,9 +514,7 @@ describe('User', function() {
|
||||||
it('Login a user by providing email credentials (email case-sensitivity off)', function(done) {
|
it('Login a user by providing email credentials (email case-sensitivity off)', function(done) {
|
||||||
User.settings.caseSensitiveEmail = false;
|
User.settings.caseSensitiveEmail = false;
|
||||||
User.login(validMixedCaseEmailCredentials, function(err, accessToken) {
|
User.login(validMixedCaseEmailCredentials, function(err, accessToken) {
|
||||||
assert(accessToken.userId);
|
assertGoodToken(accessToken, validCredentialsUser);
|
||||||
assert(accessToken.id);
|
|
||||||
assert.equal(accessToken.id.length, 64);
|
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -531,10 +530,8 @@ describe('User', function() {
|
||||||
|
|
||||||
it('Login a user by providing credentials with TTL', function(done) {
|
it('Login a user by providing credentials with TTL', function(done) {
|
||||||
User.login(validCredentialsWithTTL, function(err, accessToken) {
|
User.login(validCredentialsWithTTL, function(err, accessToken) {
|
||||||
assert(accessToken.userId);
|
assertGoodToken(accessToken, validCredentialsUser);
|
||||||
assert(accessToken.id);
|
|
||||||
assert.equal(accessToken.ttl, validCredentialsWithTTL.ttl);
|
assert.equal(accessToken.ttl, validCredentialsWithTTL.ttl);
|
||||||
assert.equal(accessToken.id.length, 64);
|
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -547,10 +544,8 @@ describe('User', function() {
|
||||||
|
|
||||||
User.findById(accessToken.userId, function(err, user) {
|
User.findById(accessToken.userId, function(err, user) {
|
||||||
user.createAccessToken(120, function(err, accessToken) {
|
user.createAccessToken(120, function(err, accessToken) {
|
||||||
assert(accessToken.userId);
|
assertGoodToken(accessToken, validCredentialsUser);
|
||||||
assert(accessToken.id);
|
|
||||||
assert.equal(accessToken.ttl, 120);
|
assert.equal(accessToken.ttl, 120);
|
||||||
assert.equal(accessToken.id.length, 64);
|
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -566,10 +561,8 @@ describe('User', function() {
|
||||||
User.findById(accessToken.userId, function(err, user) {
|
User.findById(accessToken.userId, function(err, user) {
|
||||||
user.createAccessToken(120)
|
user.createAccessToken(120)
|
||||||
.then(function(accessToken) {
|
.then(function(accessToken) {
|
||||||
assert(accessToken.userId);
|
assertGoodToken(accessToken, validCredentialsUser);
|
||||||
assert(accessToken.id);
|
|
||||||
assert.equal(accessToken.ttl, 120);
|
assert.equal(accessToken.ttl, 120);
|
||||||
assert.equal(accessToken.id.length, 64);
|
|
||||||
|
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
|
@ -588,17 +581,13 @@ describe('User', function() {
|
||||||
this.accessTokens.create({ttl: ttl / 2}, cb);
|
this.accessTokens.create({ttl: ttl / 2}, cb);
|
||||||
};
|
};
|
||||||
User.login(validCredentialsWithTTL, function(err, accessToken) {
|
User.login(validCredentialsWithTTL, function(err, accessToken) {
|
||||||
assert(accessToken.userId);
|
assertGoodToken(accessToken, validCredentialsUser);
|
||||||
assert(accessToken.id);
|
|
||||||
assert.equal(accessToken.ttl, 1800);
|
assert.equal(accessToken.ttl, 1800);
|
||||||
assert.equal(accessToken.id.length, 64);
|
|
||||||
|
|
||||||
User.findById(accessToken.userId, function(err, user) {
|
User.findById(accessToken.userId, function(err, user) {
|
||||||
user.createAccessToken(120, function(err, accessToken) {
|
user.createAccessToken(120, function(err, accessToken) {
|
||||||
assert(accessToken.userId);
|
assertGoodToken(accessToken, validCredentialsUser);
|
||||||
assert(accessToken.id);
|
|
||||||
assert.equal(accessToken.ttl, 60);
|
assert.equal(accessToken.ttl, 60);
|
||||||
assert.equal(accessToken.id.length, 64);
|
|
||||||
// Restore create access token
|
// Restore create access token
|
||||||
User.prototype.createAccessToken = createToken;
|
User.prototype.createAccessToken = createToken;
|
||||||
|
|
||||||
|
@ -617,18 +606,14 @@ describe('User', function() {
|
||||||
this.accessTokens.create({ttl: ttl / 2, scopes: options.scope}, cb);
|
this.accessTokens.create({ttl: ttl / 2, scopes: options.scope}, cb);
|
||||||
};
|
};
|
||||||
User.login(validCredentialsWithTTLAndScope, function(err, accessToken) {
|
User.login(validCredentialsWithTTLAndScope, function(err, accessToken) {
|
||||||
assert(accessToken.userId);
|
assertGoodToken(accessToken, validCredentialsUser);
|
||||||
assert(accessToken.id);
|
|
||||||
assert.equal(accessToken.ttl, 1800);
|
assert.equal(accessToken.ttl, 1800);
|
||||||
assert.equal(accessToken.id.length, 64);
|
|
||||||
assert.equal(accessToken.scopes, 'all');
|
assert.equal(accessToken.scopes, 'all');
|
||||||
|
|
||||||
User.findById(accessToken.userId, function(err, user) {
|
User.findById(accessToken.userId, function(err, user) {
|
||||||
user.createAccessToken(120, {scope: 'default'}, function(err, accessToken) {
|
user.createAccessToken(120, {scope: 'default'}, function(err, accessToken) {
|
||||||
assert(accessToken.userId);
|
assertGoodToken(accessToken, validCredentialsUser);
|
||||||
assert(accessToken.id);
|
|
||||||
assert.equal(accessToken.ttl, 60);
|
assert.equal(accessToken.ttl, 60);
|
||||||
assert.equal(accessToken.id.length, 64);
|
|
||||||
assert.equal(accessToken.scopes, 'default');
|
assert.equal(accessToken.scopes, 'default');
|
||||||
// Restore create access token
|
// Restore create access token
|
||||||
User.prototype.createAccessToken = createToken;
|
User.prototype.createAccessToken = createToken;
|
||||||
|
@ -652,13 +637,13 @@ describe('User', function() {
|
||||||
it('Login should only allow correct credentials - promise variant', function(done) {
|
it('Login should only allow correct credentials - promise variant', function(done) {
|
||||||
User.login(invalidCredentials)
|
User.login(invalidCredentials)
|
||||||
.then(function(accessToken) {
|
.then(function(accessToken) {
|
||||||
assert(!accessToken);
|
expect(accessToken, 'accessToken').to.not.exist();
|
||||||
|
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
.catch(function(err) {
|
.catch(function(err) {
|
||||||
assert(err);
|
expect(err, 'err').to.exist();
|
||||||
assert.equal(err.code, 'LOGIN_FAILED');
|
expect(err).to.have.property('code', 'LOGIN_FAILED');
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -666,8 +651,8 @@ describe('User', function() {
|
||||||
|
|
||||||
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);
|
expect(err, 'err').to.exist();
|
||||||
assert.equal(err.code, 'USERNAME_EMAIL_REQUIRED');
|
expect(err).to.have.property('code', 'USERNAME_EMAIL_REQUIRED');
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -676,13 +661,13 @@ describe('User', function() {
|
||||||
it('Login a user providing incomplete credentials - promise variant', function(done) {
|
it('Login a user providing incomplete credentials - promise variant', function(done) {
|
||||||
User.login(incompleteCredentials)
|
User.login(incompleteCredentials)
|
||||||
.then(function(accessToken) {
|
.then(function(accessToken) {
|
||||||
assert(!accessToken);
|
expect(accessToken, 'accessToken').to.not.exist();
|
||||||
|
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
.catch(function(err) {
|
.catch(function(err) {
|
||||||
assert(err);
|
expect(err, 'err').to.exist();
|
||||||
assert.equal(err.code, 'USERNAME_EMAIL_REQUIRED');
|
expect(err).to.have.property('code', 'USERNAME_EMAIL_REQUIRED');
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -699,9 +684,7 @@ describe('User', function() {
|
||||||
|
|
||||||
var accessToken = res.body;
|
var accessToken = res.body;
|
||||||
|
|
||||||
assert(accessToken.userId);
|
assertGoodToken(accessToken, validCredentialsUser);
|
||||||
assert(accessToken.id);
|
|
||||||
assert.equal(accessToken.id.length, 64);
|
|
||||||
assert(accessToken.user === undefined);
|
assert(accessToken.user === undefined);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
|
@ -811,8 +794,11 @@ describe('User', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function assertGoodToken(accessToken) {
|
function assertGoodToken(accessToken, user) {
|
||||||
assert(accessToken.userId);
|
if (accessToken instanceof AccessToken) {
|
||||||
|
accessToken = accessToken.toJSON();
|
||||||
|
}
|
||||||
|
expect(accessToken).to.have.property('userId', user.pk);
|
||||||
assert(accessToken.id);
|
assert(accessToken.id);
|
||||||
assert.equal(accessToken.id.length, 64);
|
assert.equal(accessToken.id.length, 64);
|
||||||
}
|
}
|
||||||
|
@ -880,7 +866,7 @@ describe('User', function() {
|
||||||
|
|
||||||
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, validCredentialsEmailVerifiedUser);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -889,7 +875,7 @@ describe('User', function() {
|
||||||
it('Login a user by with email verification - promise variant', function(done) {
|
it('Login a user by with email verification - promise variant', function(done) {
|
||||||
User.login(validCredentialsEmailVerified)
|
User.login(validCredentialsEmailVerified)
|
||||||
.then(function(accessToken) {
|
.then(function(accessToken) {
|
||||||
assertGoodToken(accessToken);
|
assertGoodToken(accessToken, validCredentialsEmailVerifiedUser);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
|
@ -909,7 +895,7 @@ describe('User', function() {
|
||||||
|
|
||||||
var accessToken = res.body;
|
var accessToken = res.body;
|
||||||
|
|
||||||
assertGoodToken(accessToken);
|
assertGoodToken(accessToken, validCredentialsEmailVerifiedUser);
|
||||||
assert(accessToken.user === undefined);
|
assert(accessToken.user === undefined);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
|
@ -1079,8 +1065,7 @@ describe('User', function() {
|
||||||
|
|
||||||
it('logs in a user by with realm', function(done) {
|
it('logs in a user by with realm', function(done) {
|
||||||
User.login(credentialWithRealm, function(err, accessToken) {
|
User.login(credentialWithRealm, function(err, accessToken) {
|
||||||
assertGoodToken(accessToken);
|
assertGoodToken(accessToken, user1);
|
||||||
assert.equal(accessToken.userId, user1.pk);
|
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -1088,8 +1073,7 @@ describe('User', function() {
|
||||||
|
|
||||||
it('logs in a user by with realm in username', function(done) {
|
it('logs in a user by with realm in username', function(done) {
|
||||||
User.login(credentialRealmInUsername, function(err, accessToken) {
|
User.login(credentialRealmInUsername, function(err, accessToken) {
|
||||||
assertGoodToken(accessToken);
|
assertGoodToken(accessToken, user1);
|
||||||
assert.equal(accessToken.userId, user1.pk);
|
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -1097,8 +1081,7 @@ describe('User', function() {
|
||||||
|
|
||||||
it('logs in a user by with realm in email', function(done) {
|
it('logs in a user by with realm in email', function(done) {
|
||||||
User.login(credentialRealmInEmail, function(err, accessToken) {
|
User.login(credentialRealmInEmail, function(err, accessToken) {
|
||||||
assertGoodToken(accessToken);
|
assertGoodToken(accessToken, user1);
|
||||||
assert.equal(accessToken.userId, user1.pk);
|
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -1115,8 +1098,7 @@ describe('User', function() {
|
||||||
|
|
||||||
it('logs in a user by with realm', function(done) {
|
it('logs in a user by with realm', function(done) {
|
||||||
User.login(credentialWithRealm, function(err, accessToken) {
|
User.login(credentialWithRealm, function(err, accessToken) {
|
||||||
assertGoodToken(accessToken);
|
assertGoodToken(accessToken, user1);
|
||||||
assert.equal(accessToken.userId, user1.pk);
|
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -1139,7 +1121,7 @@ describe('User', function() {
|
||||||
login(logout);
|
login(logout);
|
||||||
|
|
||||||
function login(fn) {
|
function login(fn) {
|
||||||
User.login({email: 'foo@bar.com', password: 'bar'}, fn);
|
User.login(validCredentials, fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
function logout(err, accessToken) {
|
function logout(err, accessToken) {
|
||||||
|
@ -1152,7 +1134,7 @@ describe('User', function() {
|
||||||
login(logout);
|
login(logout);
|
||||||
|
|
||||||
function login(fn) {
|
function login(fn) {
|
||||||
User.login({email: 'foo@bar.com', password: 'bar'}, fn);
|
User.login(validCredentials, fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
function logout(err, accessToken) {
|
function logout(err, accessToken) {
|
||||||
|
@ -1171,14 +1153,12 @@ describe('User', function() {
|
||||||
.post('/test-users/login')
|
.post('/test-users/login')
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.send({email: 'foo@bar.com', password: 'bar'})
|
.send(validCredentials)
|
||||||
.end(function(err, res) {
|
.end(function(err, res) {
|
||||||
if (err) return done(err);
|
if (err) return done(err);
|
||||||
|
|
||||||
var accessToken = res.body;
|
var accessToken = res.body;
|
||||||
|
assertGoodToken(accessToken, validCredentialsUser);
|
||||||
assert(accessToken.userId);
|
|
||||||
assert(accessToken.id);
|
|
||||||
|
|
||||||
fn(null, accessToken.id);
|
fn(null, accessToken.id);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue