Initial auth implementation
This commit is contained in:
parent
3eb04f64c3
commit
2f9403016c
|
@ -43,12 +43,6 @@ app.disuse = function (route) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* App models.
|
|
||||||
*/
|
|
||||||
|
|
||||||
app._models = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expose a model.
|
* Expose a model.
|
||||||
*
|
*
|
||||||
|
@ -60,7 +54,7 @@ app.model = function (Model, config) {
|
||||||
assert(typeof Model === 'function', 'app.model(Model) => Model must be a function / constructor');
|
assert(typeof Model === 'function', 'app.model(Model) => Model must be a function / constructor');
|
||||||
assert(Model.pluralModelName, 'Model must have a "pluralModelName" property');
|
assert(Model.pluralModelName, 'Model must have a "pluralModelName" property');
|
||||||
this.remotes().exports[Model.pluralModelName] = Model;
|
this.remotes().exports[Model.pluralModelName] = Model;
|
||||||
this._models.push(Model);
|
this.models().push(Model);
|
||||||
Model.shared = true;
|
Model.shared = true;
|
||||||
Model.app = this;
|
Model.app = this;
|
||||||
Model.emit('attached', this);
|
Model.emit('attached', this);
|
||||||
|
@ -82,20 +76,12 @@ app.model = function (Model, config) {
|
||||||
return Model;
|
return Model;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a Model by name.
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.getModel = function (modelName) {
|
|
||||||
this.models
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all exposed models.
|
* Get all exposed models.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
app.models = function () {
|
app.models = function () {
|
||||||
return this._models;
|
return this._models || (this._models = []);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -144,7 +130,6 @@ app.docs = function (options) {
|
||||||
swagger(remotes, options);
|
swagger(remotes, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Get a handler of the specified type from the handler cache.
|
* Get a handler of the specified type from the handler cache.
|
||||||
*/
|
*/
|
||||||
|
@ -166,6 +151,52 @@ app.handler = function (type) {
|
||||||
|
|
||||||
app.dataSources = app.datasources = {};
|
app.dataSources = app.datasources = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable app wide authentication.
|
||||||
|
*/
|
||||||
|
|
||||||
|
app.enableAuth = function() {
|
||||||
|
var remotes = this.remotes();
|
||||||
|
|
||||||
|
remotes.before('**', function(ctx, next, method) {
|
||||||
|
var req = ctx.req;
|
||||||
|
var Model = method.ctor;
|
||||||
|
var modelInstance = ctx.instance;
|
||||||
|
var modelId = modelInstance && modelInstance.id;
|
||||||
|
|
||||||
|
// TODO(ritch) - this fallback could be less express dependent
|
||||||
|
if(modelInstance && !modelId) {
|
||||||
|
modelId = req.param('id');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(req.accessToken) {
|
||||||
|
Model.checkAccess(
|
||||||
|
req.accessToken,
|
||||||
|
modelId,
|
||||||
|
method.name,
|
||||||
|
function(err, allowed) {
|
||||||
|
if(err) {
|
||||||
|
next(err);
|
||||||
|
} else if(allowed) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
var e = new Error('Access Denied');
|
||||||
|
e.statusCode = 401;
|
||||||
|
next(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else if(method.fn && method.fn.requireToken === false) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
var e = new Error('Access Denied');
|
||||||
|
e.statusCode = 401;
|
||||||
|
|
||||||
|
next(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the app using JSON and JavaScript files.
|
* Initialize the app using JSON and JavaScript files.
|
||||||
*
|
*
|
||||||
|
|
|
@ -252,7 +252,6 @@ loopback.RoleMapping = require('./models/role').RoleMapping;
|
||||||
loopback.ACL = require('./models/acl').ACL;
|
loopback.ACL = require('./models/acl').ACL;
|
||||||
loopback.Scope = require('./models/acl').Scope;
|
loopback.Scope = require('./models/acl').Scope;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Automatically attach these models to dataSources
|
* Automatically attach these models to dataSources
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -8,7 +8,8 @@ var Model = require('../loopback').Model
|
||||||
, crypto = require('crypto')
|
, crypto = require('crypto')
|
||||||
, uid = require('uid2')
|
, uid = require('uid2')
|
||||||
, DEFAULT_TTL = 1209600 // 2 weeks in seconds
|
, DEFAULT_TTL = 1209600 // 2 weeks in seconds
|
||||||
, DEFAULT_TOKEN_LEN = 64;
|
, DEFAULT_TOKEN_LEN = 64
|
||||||
|
, ACL = require('./acl').ACL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default AccessToken properties.
|
* Default AccessToken properties.
|
||||||
|
|
|
@ -274,6 +274,10 @@ Scope.checkPermission = function (scope, model, property, accessType, callback)
|
||||||
ACL.checkAccess = function (context, callback) {
|
ACL.checkAccess = function (context, callback) {
|
||||||
context = context || {};
|
context = context || {};
|
||||||
var principals = context.principals || [];
|
var principals = context.principals || [];
|
||||||
|
|
||||||
|
// add ROLE.EVERYONE
|
||||||
|
principals.unshift({principalType: ACL.ROLE, principalId: Role.EVERYONE});
|
||||||
|
|
||||||
var model = context.model;
|
var model = context.model;
|
||||||
model = ('string' === typeof model) ? loopback.getModel(model) : model;
|
model = ('string' === typeof model) ? loopback.getModel(model) : model;
|
||||||
var id = context.id;
|
var id = context.id;
|
||||||
|
@ -369,7 +373,7 @@ ACL.checkAccessForToken = function(token, model, modelId, method, callback) {
|
||||||
callback && callback(err);
|
callback && callback(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
callback && callback(access.permission !== ACL.DENY);
|
callback && callback(null, access.permission !== ACL.DENY);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
var loopback = require('../');
|
var loopback = require('../');
|
||||||
var Token = loopback.AccessToken.extend('MyToken');
|
var Token = loopback.AccessToken.extend('MyToken');
|
||||||
|
var ACL = loopback.ACL;
|
||||||
|
|
||||||
describe('loopback.token(options)', function() {
|
describe('loopback.token(options)', function() {
|
||||||
beforeEach(createTestingToken);
|
beforeEach(createTestingToken);
|
||||||
|
@ -54,6 +55,27 @@ describe('AccessToken', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('app.enableAuth()', function() {
|
||||||
|
this.timeout(0);
|
||||||
|
|
||||||
|
beforeEach(createTestingToken);
|
||||||
|
|
||||||
|
it('should prevent all remote method calls without an accessToken', function (done) {
|
||||||
|
createTestAppAndRequest(this.token, done)
|
||||||
|
.get('/tests')
|
||||||
|
.expect(401)
|
||||||
|
.end(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should prevent remote method calls if the accessToken doesnt have access', function (done) {
|
||||||
|
createTestAppAndRequest(this.token, done)
|
||||||
|
.del('/tests/123')
|
||||||
|
.expect(401)
|
||||||
|
.set('authorization', this.token.id)
|
||||||
|
.end(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function createTestingToken(done) {
|
function createTestingToken(done) {
|
||||||
var test = this;
|
var test = this;
|
||||||
Token.create({}, function (err, token) {
|
Token.create({}, function (err, token) {
|
||||||
|
@ -86,6 +108,23 @@ function createTestApp(testToken, done) {
|
||||||
}
|
}
|
||||||
res.send('ok');
|
res.send('ok');
|
||||||
});
|
});
|
||||||
|
app.use(loopback.rest());
|
||||||
|
app.enableAuth();
|
||||||
|
|
||||||
|
var TestModel = loopback.Model.extend('test', {}, {
|
||||||
|
acls: [
|
||||||
|
{
|
||||||
|
principalType: "ROLE",
|
||||||
|
principalId: "$everyone",
|
||||||
|
accessType: ACL.ALL,
|
||||||
|
permission: ACL.DENY,
|
||||||
|
property: 'removeById'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
TestModel.attachTo(loopback.memory());
|
||||||
|
app.model(TestModel);
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,6 @@ describe('User', function(){
|
||||||
User.setMaxListeners(0);
|
User.setMaxListeners(0);
|
||||||
|
|
||||||
before(function () {
|
before(function () {
|
||||||
debugger;
|
|
||||||
User.hasMany(AccessToken, {as: 'accessTokens', foreignKey: 'userId'});
|
User.hasMany(AccessToken, {as: 'accessTokens', foreignKey: 'userId'});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -257,8 +256,7 @@ describe('User', function(){
|
||||||
assert(result.token);
|
assert(result.token);
|
||||||
|
|
||||||
|
|
||||||
var lines = result.email.message.split('\n');
|
assert(~result.email.message.indexOf('To: bar@bat.com'));
|
||||||
assert(lines[3].indexOf('To: bar@bat.com') === 0);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue