Add realm support
This commit is contained in:
parent
f511bd38c9
commit
46d1430023
|
@ -5,6 +5,9 @@
|
|||
"type": "string",
|
||||
"id": true
|
||||
},
|
||||
"realm": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
|
|
|
@ -57,6 +57,57 @@ User.prototype.createAccessToken = function(ttl, cb) {
|
|||
}, cb);
|
||||
};
|
||||
|
||||
function splitPrincipal(name, realmDelimiter) {
|
||||
var parts = [null, name];
|
||||
if(!realmDelimiter) {
|
||||
return parts;
|
||||
}
|
||||
var index = name.indexOf(realmDelimiter);
|
||||
if (index !== -1) {
|
||||
parts[0] = name.substring(0, index);
|
||||
parts[1] = name.substring(index + realmDelimiter.length);
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the credentials
|
||||
* @param {Object} credentials The credential object
|
||||
* @param {Boolean} realmRequired
|
||||
* @param {String} realmDelimiter The realm delimiter, if not set, no realm is needed
|
||||
* @returns {Object} The normalized credential object
|
||||
*/
|
||||
User.normalizeCredentials = function(credentials, realmRequired, realmDelimiter) {
|
||||
var query = {};
|
||||
credentials = credentials || {};
|
||||
if(!realmRequired) {
|
||||
if (credentials.email) {
|
||||
query.email = credentials.email;
|
||||
} else if (credentials.username) {
|
||||
query.username = credentials.username;
|
||||
}
|
||||
} else {
|
||||
if (credentials.realm) {
|
||||
query.realm = credentials.realm;
|
||||
}
|
||||
var parts;
|
||||
if (credentials.email) {
|
||||
parts = splitPrincipal(credentials.email, realmDelimiter);
|
||||
query.email = parts[1];
|
||||
if (parts[0]) {
|
||||
query.realm = parts[0];
|
||||
}
|
||||
} else if (credentials.username) {
|
||||
parts = splitPrincipal(credentials.username, realmDelimiter);
|
||||
query.username = parts[1];
|
||||
if (parts[0]) {
|
||||
query.realm = parts[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Login a user by with the given `credentials`.
|
||||
*
|
||||
|
@ -88,16 +139,25 @@ User.login = function(credentials, include, fn) {
|
|||
include = include.toLowerCase();
|
||||
}
|
||||
|
||||
var realmDelimiter;
|
||||
// Check if realm is required
|
||||
var realmRequired = !!(self.settings.realmRequired ||
|
||||
self.settings.realmDelimiter);
|
||||
if (realmRequired) {
|
||||
realmDelimiter = self.settings.realmDelimiter;
|
||||
}
|
||||
var query = self.normalizeCredentials(credentials, realmRequired,
|
||||
realmDelimiter);
|
||||
|
||||
var query = {};
|
||||
if (credentials.email) {
|
||||
query.email = credentials.email;
|
||||
} else if (credentials.username) {
|
||||
query.username = credentials.username;
|
||||
} else {
|
||||
var err = new Error('username or email is required');
|
||||
err.statusCode = 400;
|
||||
return fn(err);
|
||||
if(realmRequired && !query.realm) {
|
||||
var err1 = new Error('realm is required');
|
||||
err1.statusCode = 400;
|
||||
return fn(err1);
|
||||
}
|
||||
if (!query.email && !query.username) {
|
||||
var err2 = new Error('username or email is required');
|
||||
err2.statusCode = 400;
|
||||
return fn(err2);
|
||||
}
|
||||
|
||||
self.findOne({where: query}, function(err, user) {
|
||||
|
@ -488,9 +548,14 @@ User.setup = function() {
|
|||
// email validation regex
|
||||
var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
|
||||
UserModel.validatesUniquenessOf('email', {message: 'Email already exists'});
|
||||
|
||||
UserModel.validatesFormatOf('email', {with: re, message: 'Must provide a valid email'});
|
||||
|
||||
// FIXME: We need to add support for uniqueness of composite keys in juggler
|
||||
if (!(UserModel.settings.realmRequired || UserModel.settings.realmDelimiter)) {
|
||||
UserModel.validatesUniquenessOf('email', {message: 'Email already exists'});
|
||||
UserModel.validatesUniquenessOf('username', {message: 'User already exists'});
|
||||
}
|
||||
|
||||
return UserModel;
|
||||
}
|
||||
|
|
|
@ -292,6 +292,12 @@ describe('User', function(){
|
|||
});
|
||||
});
|
||||
|
||||
function assertGoodToken(accessToken) {
|
||||
assert(accessToken.userId);
|
||||
assert(accessToken.id);
|
||||
assert.equal(accessToken.id.length, 64);
|
||||
}
|
||||
|
||||
describe('User.login requiring email verification', function() {
|
||||
beforeEach(function() {
|
||||
User.settings.emailVerificationRequired = true;
|
||||
|
@ -310,9 +316,7 @@ describe('User', function(){
|
|||
|
||||
it('Login a user by with email verification', function(done) {
|
||||
User.login(validCredentialsEmailVerified, function (err, accessToken) {
|
||||
assert(accessToken.userId);
|
||||
assert(accessToken.id);
|
||||
assert.equal(accessToken.id.length, 64);
|
||||
assertGoodToken(accessToken);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -327,9 +331,7 @@ describe('User', function(){
|
|||
if(err) return done(err);
|
||||
var accessToken = res.body;
|
||||
|
||||
assert(accessToken.userId);
|
||||
assert(accessToken.id);
|
||||
assert.equal(accessToken.id.length, 64);
|
||||
assertGoodToken(accessToken);
|
||||
assert(accessToken.user === undefined);
|
||||
|
||||
done();
|
||||
|
@ -349,6 +351,166 @@ describe('User', function(){
|
|||
|
||||
});
|
||||
|
||||
describe('User.login requiring realm', function() {
|
||||
var User, AccessToken;
|
||||
|
||||
before(function() {
|
||||
User = loopback.User.extend('RealmUser', {},
|
||||
{realmRequired: true, realmDelimiter: ':'});
|
||||
AccessToken = loopback.AccessToken.extend('RealmAccessToken');
|
||||
|
||||
loopback.autoAttach();
|
||||
|
||||
// Update the AccessToken relation to use the subclass of User
|
||||
AccessToken.belongsTo(User);
|
||||
User.hasMany(AccessToken);
|
||||
|
||||
// allow many User.afterRemote's to be called
|
||||
User.setMaxListeners(0);
|
||||
});
|
||||
|
||||
var realm1User = {
|
||||
realm: 'realm1',
|
||||
username: 'foo100',
|
||||
email: 'foo100@bar.com',
|
||||
password: 'pass100'
|
||||
};
|
||||
|
||||
var realm2User = {
|
||||
realm: 'realm2',
|
||||
username: 'foo100',
|
||||
email: 'foo100@bar.com',
|
||||
password: 'pass200'
|
||||
};
|
||||
|
||||
var credentialWithoutRealm = {
|
||||
username: 'foo100',
|
||||
email: 'foo100@bar.com',
|
||||
password: 'pass100'
|
||||
};
|
||||
|
||||
var credentialWithBadPass = {
|
||||
realm: 'realm1',
|
||||
username: 'foo100',
|
||||
email: 'foo100@bar.com',
|
||||
password: 'pass001'
|
||||
};
|
||||
|
||||
var credentialWithBadRealm = {
|
||||
realm: 'realm3',
|
||||
username: 'foo100',
|
||||
email: 'foo100@bar.com',
|
||||
password: 'pass100'
|
||||
};
|
||||
|
||||
var credentialWithRealm = {
|
||||
realm: 'realm1',
|
||||
username: 'foo100',
|
||||
password: 'pass100'
|
||||
};
|
||||
|
||||
var credentialRealmInUsername = {
|
||||
username: 'realm1:foo100',
|
||||
password: 'pass100'
|
||||
};
|
||||
|
||||
var credentialRealmInEmail = {
|
||||
email: 'realm1:foo100@bar.com',
|
||||
password: 'pass100'
|
||||
};
|
||||
|
||||
var user1;
|
||||
beforeEach(function(done) {
|
||||
User.create(realm1User, function(err, u) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
user1 = u;
|
||||
User.create(realm2User, done);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function(done) {
|
||||
User.deleteAll({realm: 'realm1'}, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
User.deleteAll({realm: 'realm2'}, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects a user by without realm', function(done) {
|
||||
User.login(credentialWithoutRealm, function(err, accessToken) {
|
||||
assert(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects a user by with bad realm', function(done) {
|
||||
User.login(credentialWithBadRealm, function(err, accessToken) {
|
||||
assert(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects a user by with bad pass', function(done) {
|
||||
User.login(credentialWithBadPass, function(err, accessToken) {
|
||||
assert(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('logs in a user by with realm', function(done) {
|
||||
User.login(credentialWithRealm, function(err, accessToken) {
|
||||
assertGoodToken(accessToken);
|
||||
assert.equal(accessToken.userId, user1.id);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('logs in a user by with realm in username', function(done) {
|
||||
User.login(credentialRealmInUsername, function(err, accessToken) {
|
||||
assertGoodToken(accessToken);
|
||||
assert.equal(accessToken.userId, user1.id);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('logs in a user by with realm in email', function(done) {
|
||||
User.login(credentialRealmInEmail, function(err, accessToken) {
|
||||
assertGoodToken(accessToken);
|
||||
assert.equal(accessToken.userId, user1.id);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('User.login with realmRequired but no realmDelimiter', function() {
|
||||
before(function() {
|
||||
User.settings.realmDelimiter = undefined;
|
||||
});
|
||||
|
||||
after(function() {
|
||||
User.settings.realmDelimiter = ':';
|
||||
});
|
||||
|
||||
it('logs in a user by with realm', function(done) {
|
||||
User.login(credentialWithRealm, function(err, accessToken) {
|
||||
assertGoodToken(accessToken);
|
||||
assert.equal(accessToken.userId, user1.id);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects a user by with realm in email if realmDelimiter is not set',
|
||||
function(done) {
|
||||
User.login(credentialRealmInEmail, function(err, accessToken) {
|
||||
assert(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('User.logout', function() {
|
||||
it('Logout a user by providing the current accessToken id (using node)', function(done) {
|
||||
login(logout);
|
||||
|
|
Loading…
Reference in New Issue