Skip to content

Instantly share code, notes, and snippets.

@pulkitsinghal
Last active December 8, 2015 22:47
Show Gist options
  • Save pulkitsinghal/e0c64e647060c205a1e6 to your computer and use it in GitHub Desktop.
Save pulkitsinghal/e0c64e647060c205a1e6 to your computer and use it in GitHub Desktop.
How to set user in loopback context for use by remote methods when an arbitrary app token is used by incoming requests?
// it resides in `/server/boot/` directory
'use strict';
var path = require('path');
var fileName = path.basename(__filename, '.js'); // gives the filename without the .js extension
var log = require('debug')('boot:'+fileName);
module.exports = function(app) {
var Role = app.models.Role;
Role.registerResolver('customRoleResolver', function(role, context, cb) {
log('inside customRoleResolver');
log('role', role);
log('context.modelName: ', context.modelName);
//log('context.accessToken: ', context.accessToken);
var appToken = context.remotingContext.req.header('X-APP-TOKEN');
if (!appToken) {
appToken = context.remotingContext.req.query['appToken'];
log('context.remotingContext.req.query[appToken]: ', appToken);
}
else {
log('context.remotingContext.req.header(\'X-APP-TOKEN\'): ', appToken);
}
function reject() {
process.nextTick(function() {
cb(null, false); // `false` means DENY access;
});
}
// TODO: test IF any MyModel has a match for given appToken
var MyModel = app.models.MyModel;
MyModel.findOne({where: {'integrations.thirdPartyAppName.token': appToken} },
function(err, myModel){
log('inside findOne callback', 'myModel:', myModel);
if (err) {
log('ERROR', err);
return reject();
}
if (!myModel) {
log('ERROR', 'No MyModel associated with this appToken was found.');
return reject();
}
var loopback = require('loopback'); // NOTE: moving it to the global scope of this file leads to an undefined loopbackContext
var loopbackContext = loopback.getCurrentContext();
if (loopbackContext) {
log('loopbackContext', loopbackContext);
loopbackContext.set('currentUser', myModel); // info for use by remote methods
cb(null, true); // `true` means ALLOW access;
}
else {
log('ERROR', 'Why is there no loopbackContext? `customRoleResolver` cannot work without it');
return reject();
}
// TODO: the remote method is still responsible for filtering the results by currentUser's id
});
});
};
var path = require('path');
var fileName = path.basename(__filename, '.js'); // gives the filename without the .js extension
var log = require('debug')('common:models:'+fileName);
module.exports = function(SomeModel) {
SomeModel.remoteMethod('myRemoteMethodName', {
accepts: [],
http: {path: '/myRemoteMethodName', verb: 'get'},
returns: {type: 'object', root: true}
});
SomeModel.myRemoteMethodName = function(cb) {
log('SomeModel.myRemoteMethodName');
var currentUser = SomeModel.getCurrentUserModel(cb); // returns immediately if no currentUser
if (currentUser) {
var filters = {
where: {
and: [
{ sellerModelId: currentUser.id}/*,
{ other: 'thingsYouCareAbout' }*/
]
}
};
log('SomeModel.myRemoteMethodName', 'filters:', filters);
// filter results by running Model.find(filters);
cb(null);
}
};
};
{
"name": "SomeModel",
...
"acls": [
...
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY",
"property": "myRemoteMethodName"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "customRoleResolver",
"permission": "ALLOW",
"property": "myRemoteMethodName"
}
],
"mixins": {
"Utils": true
}
}
// it resides in `/common/mixins/` directory
'use strict';
/* jshint ignore:start */
var loopback = require('loopback');
var Promise = require('bluebird'); // jshint ignore:line
var path = require('path');
var fileName = path.basename(__filename, '.js'); // gives the filename without the .js extension
//var log = require('./../lib/debug-extension')('common:models:'+fileName);
var log = require('debug')('common:models:'+fileName);
module.exports = function(Model, options) {
Model.getCurrentUserModel = function(cb) {
var ctx = loopback.getCurrentContext();
var currentUser = ctx && ctx.get('currentUser');
if (currentUser) {
log('inside ' + Model.definition.name + '.getCurrentUserModel() - currentUser: ', currentUser.username);
//return currentUser;
return Promise.promisifyAll(
currentUser,
{
filter: function(name, func, target){
return (name !== 'validate');
}
}
);
}
else {
// TODO: when used with core invocations, the call stack can end up here
// this error only makes sense to point out failures in RESTful calls
// how can this sanity check be made any better?
cb('401 - unauthorized - how did we end up here? should we manage ACL access to remote methods ourselves?');
}
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment