strongloop / loopback

LoopBack makes it easy to build modern applications that require complex integrations.
http://loopback.io
Other
13.22k stars 1.2k forks source link

Injecting Remote Context via Options #1495

Closed ritch closed 7 years ago

ritch commented 9 years ago

UPDATE 2016-12-22 We ended up implementing a different solution as described in https://github.com/strongloop/loopback/pull/3023.

Tasks

Original description for posterity

The example below allows you to inject the remote context object (from strong remoting) into all methods that accept an options argument.


function inject(ctx, next) {
  var options = hasOptions(ctx.method.accepts) && (ctx.args.options || {});
  if(options) {
    options.remoteCtx = ctx;
    ctx.args.options = options;
  }
  next();
}

app.remotes().before('*.*', inject);

app.remotes().before('*.prototype.*', function(ctx, instance, next) {
  inject(ctx, next);
});

// unfortunately this requires us to add the options object
// to the remote method definition
app.remotes().methods().forEach(function(method) {
  if(!hasOptions(method.accepts)) {
    method.accepts.push({
      arg: 'options',
      type: 'object',
      injectCtx: true
    });
  }
});

function hasOptions(accepts) {
  for (var i = 0; i < accepts.length; i++) {
    var argDesc = accepts[i];
    if (argDesc.arg === 'options' && argDesc.injectCtx) {
      return true;
    }
  }
}

Why? So you can use the remote context in remote methods, operation hooks, connector implementation, etc.

MyModel.observe('before save', function(ctx, next) {
  console.log(ctx.options.remoteCtx.accessToken.userId); // current user
});

This approach is specifically designed to allow you to do what is possible with loopback.getCurrentContext() but without the dependency on cls. The injection approach is much simpler and should be quite a bit faster since cls adds some significant overhead.

@bajtos @fabien @raymondfeng

zanemcca commented 8 years ago

+1 for this method being a poor solution.

I refactored all of my code and tests after this popped up randomly only to discover that it does not work on related methods like contractor.prototype.__create__contract.

It appears that the options arg is not being persisted for related models. You can check method.accepts during runtime and see that it does not have options even after verifying that it is pushed on in this code.

app.remotes().methods().forEach(function(method) {
    if(!hasOptions(method.accepts)) {
        method.accepts.push({
            arg: 'options',
            description: '**Do not implement in clients**',
            type: 'object',
            injectCtx: true
        }); 
    }   
});
zanemcca commented 8 years ago

For anyone who is interested here is an updated version of the hack that takes into account all of the previously listed errors, except for the related methods error.

  /*  
   * Sets up options injection for passing accessToken and other properties around internally
   */
  var setupOptionsInjection = function() {
    function inject(ctx, next) {
      var options = hasOptions(ctx.method.accepts) && (ctx.args.options || {});
      if(options) {
        options.accessToken = ctx.req.accessToken;
        ctx.args.options = options;
      }   
      next();
    }   
    function hasOptions(accepts) {
      for (var i = 0; i < accepts.length; i++) {
        var argDesc = accepts[i];
        if (argDesc.arg === 'options' && argDesc.injectCtx) {
          return true;
        }   
      }   
    }   

    if(!process.env.GENERATING_SDK) {
      app.remotes().before('*.*', inject);

      app.remotes().before('*.prototype.*', function(ctx, instance, next) {
        if (typeof instance === 'function') {
          next = instance;
        }   
        inject(ctx, next);
      }); 

      var blacklist = ['login', 'logout', 'confirm', 'resetPassword'];

      // unfortunately this requires us to add the options object
      // to the remote method definition
      app.remotes().methods().forEach(function(method) {
        if(!hasOptions(method.accepts) && blacklist.indexOf(method.name) === -1) {
          method.accepts.push({
            arg: 'options',
            description: '**Do not implement in clients**',
            type: 'object',
            injectCtx: true
          }); 
        }   
      }); 
    }   
  };  

This takes into account the bug pointed out by @wprater and a fix for it by @digitalsadhu. It also adds a blacklist that will ignore certain functions such as login and logout.This fixes the issues noticed by @ryedin and others. The GENERATING_SDK env var can be set before running lb-ng to avoid having options in the angular sdk.

zanemcca commented 8 years ago

@richardpringle is there any update on a fix for this issue?

guanbo commented 8 years ago

As @hotaru355 @zanemcca mention to "model.prototype.createrelation" not work for @ritch 's solution. Because prototype.methods resolve by dynamically

https://github.com/strongloop/strong-remoting/blob/master/lib/shared-class.js#L130

My solution is walking around by acl.

module.exports = function(app) {

  var Role = app.models.Role;
  var merchantStaffRoles = ['owner', 'manager', 'cashier'];

  function checkOptions(method) {
    if(!hasOptions(method.accepts)) {
      method.accepts.push({
        arg: 'options',
        type: 'object',
        injectCtx: true
      });
    }
  }

  function hasOptions(accepts) {
    for (var i = 0; i < accepts.length; i++) {
      var argDesc = accepts[i];
      if (argDesc.arg === 'options' && argDesc.injectCtx) {
        return true;
      }
    }
  }

  function errorWithStatus(msg, status) {
    var error = new Error(msg);
    error.status = status;
    return error;
  }

  // Inspire by https://github.com/strongloop/loopback/issues/1495
  Role.registerResolver('merchantStaff', function (role, ctx, next) {
    if(!ctx.accessToken.userId) return process.nextTick(()=>next(errorWithStatus('Forbidden anonymouse user', 401), false));
    app.models.User.findById(ctx.accessToken.userId, function(err, user) {
      if (err || !user) {
        if(!user) err = errorWithStatus('Forbidden anonymouse user', 401);
        return next(err, false);
      }
      checkOptions(ctx.sharedMethod);
      var options = ctx.remotingContext.args.options||{};
      options.currentUser = user;
      ctx.remotingContext.args.options = options;
      next(err, merchantStaffRoles.indexOf(user.role)>-1);
    });
  });

  // Another poor solution, not work for model.prototype.__create__relation
  // https://github.com/snowyu/loopback-component-remote-ctx.js

};
pongstr commented 8 years ago

Are there any updates on this issue?

skyserpent commented 8 years ago

Why is this still a problem? We're about to go live with our app which has been in development for months and now this happens... Damn, this is not good at ALL...

snowyu commented 8 years ago

In fact, I do not like this tricky. Why not to pass the current-context as an optional argument to the model's event directly.

richardpringle commented 8 years ago

I agree that the each operation should have the remoting context when triggered by a user.

The scope of this change could be pretty big though and will have to be evaluated.

@raymondfeng @ritch @bajtos, what do you guys think?

dongmai commented 8 years ago

How can I access the remote context in a middle ware, it just have (req, res, next) ?

pkgulati commented 8 years ago

I used monkey patching of strong remoting SharedMethod.prototype.invoke method to inject options, similar to callback. We can set req.contextOptions in middlewares, this gets passed to juggler and you can receive this in all observer hooks or user hooks.

        var injectedOptions = ctx.req.contextOptions
        formattedArgs.push(injectedOptions);

        // add in the required callback
        formattedArgs.push(callback);
bajtos commented 8 years ago

@souluniversal

Can I lobby to have the issues with getCurrentContext() and this suggestion/issue page etc referenced in the loopback docs (e.g., https://docs.strongloop.com/display/public/LB/Using+current+context)?

Could save others some serious headaches.

Good idea, done.

josieusa commented 8 years ago

~Since this issue is now referenced in the docs, I think it's good to reference yet another (completely different) alternative, even if not done (yet)~ ~https://github.com/strongloop/loopback-context/pull/2~ ~TL;DR; use the native-ish cls-hooked(which is good for Node 6 and future Node releases) instead of continuation-local-storage, and detect (unfortunately not at compile time) a very common source of bugs with cls which were successfully reproduced (maybe only a subset of the possible bugs, so be warned).~ PR closed

bajtos commented 8 years ago

FWIW: if you are writing a custom remote method and all you need is currentUserId, then the following approach may work well for you:

MyModel.customFunc = function(data, currentUserId, cb) {
  // ...
};

MyModel.remoteMethod('customFunc', {
  accepts: [
    // arguments supplied by the HTTP client
    { arg: 'data', type: 'object', required: true, http: { source: 'body' } },
    // arguments inferred from the request context
    { 
      arg: currentUserId, type: 'any', 
      // EDITED
      http: function(ctx) { return ctx.req.accessToken && ctx.req.accessToken.userId; }
      // doesn't work
      // http: function(req) { return req.accessToken && req.accessToken.userId; }
    },
  ],
  // ...
});
bajtos commented 8 years ago

@dongmai

How can I access the remote context in a middle ware, it just have (req, res, next) ?

When using loopback-context#per-request, you can use req.loopbackContext to access the CLS container that will be returned by LoopBackContext.getCurrentContext().

bajtos commented 8 years ago

@dongmai

I think I misunderstood what you are asking. I don't think there is an easy way how to access remoting context from a middleware. Middleware typically stores data on the req object. Then you can create a remoting hook to process the data attached on the req object and make any changes to the remoting context as needed.

Saganus commented 8 years ago

Any updates on this issue?

I was also about to deploy to production when the context started to get lost. I have tried using the npm-shrinkwrap solution but it's not working for me.

Any particular version that I should stick to for continuation-local-storage?

Currently, my npm-shrinkwrap file contains the following cls version:

"continuation-local-storage": {
          "version": "3.1.7",
          "from": "continuation-local-storage@>=3.1.3 <4.0.0",
          "resolved": "https://registry.npmjs.org/continuation-local-storage/-/continuation-local-storage-3.1.7.tgz",
          "dependencies": {
            "async-listener": {
              "version": "0.6.3",
              "from": "async-listener@>=0.6.0 <0.7.0",
              "resolved": "https://registry.npmjs.org/async-listener/-/async-listener-0.6.3.tgz",
              "dependencies": {
                "shimmer": {
                  "version": "1.0.0",
                  "from": "shimmer@1.0.0",
                  "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.0.0.tgz"
                }
              }
            },

Are there any particular steps I should follow to make npm-shrinkwrap alleviate this problem? or are there any other potential workarounds? it's very furstrating because it seems that sometimes after re-deploying several times it ends up working.

Thanks!

JoeShi commented 8 years ago

@bajtos I have tried your solution. the following is my code.

ip and userId is undefined

  Card.remoteMethod('preorder', {
    isStatic: false,
    accepts: [
      {
        arg: 'ip', type: 'any',
        http: function (req) {return req.ip}
      },
      {
        arg: 'userId', type: 'any',
        http: function (req) {return req.accessToken && req.accessToken.userId}
      }
    ],
    returns: [
      {arg: 'appId', type: 'string', description: '公众号名称'},
      {arg: 'timeStamp', type: 'string'},
      {arg: 'nonceStr', type: 'string'},
      {arg: 'package', type: 'string'},
      {arg: 'signType', type: 'string'},
      {arg: 'paySign', type: 'string'}
    ],
    http: {path: '/preorder', verb: 'post', errorStatus: 400}
  });

  Card.prototype.preorder = function (ip, userId, callback) {

    console.log(ip);
    console.log(userId);
}
Saganus commented 8 years ago

@JoeShi

Same thing happened to me when trying to return the accessToken. One thing I've noted in the past is that for some reason req doesn't seem to have attached the accessToken object. At least it's not always available. I'm not sure why.

I'm thinking on having to parse the query to get the access_token and then just query the DB to get the userId corresponding to that token and use that instead. Unfortunately it will add another query for each call.

pulkitsinghal commented 8 years ago

Disclaimer: I plan to do the same for options injection too soon. I'm not trying to hijack the original intent of this issue.

Here's an idea for those who are still sticking with CLS:

  1. If the following people can share their exact versions for nodejs, npm and the output file called npm-shrinkwrap.json after running npm shrinkwrap then I can look into setting up all of the permutations on Docker and publish a table of results around which ones work and which ones don't.
    1. @Saganus, @skyserpent , @pongstr , @snowyu, @mrfelton and @tomhallam ... I think that's everyone on this thread who's in bed with cls ... if I guessed wrong, my apologies.
  2. Any simplified code (based on your usecases) that you can share for quickly testing if CLS is working, would also be appreciated! I will volume-mount such code into all the docker containers and then maybe use apache-ab or something to test the server. If someone has unit tests, or functional test so that I don't have to build a test harness, even better!
  3. I understand that connectors are a big part of the puzzle so I hope to also test across mongo and memory. If you want me to test additional ones then you may have to help me out a bit by giving me a simple sandbox env that's ready to go. For example, I prepared ones for connectors that I'm familiar with:
    1. mongo - https://github.com/ShoppinPal/loopback-mongo-sandbox/blob/master/docker-compose.yml
    2. elasticsearch - https://github.com/strongloop-community/loopback-connector-elastic-search/blob/master/docker-compose-for-tests.yml

I'm volunteering for running a large-scale experiment to narrow down the issue of what works most easily, therefore, anything you can pitch in to the trial will help.

digitalsadhu commented 8 years ago

Fwiw we have stopped using currentContext as well as operation hooks and never looked back. Have yet to come across a situation where this has been a problem for us. (We have a half dozen medium to large loopback apps in production)

You can always get ahold of req inside custom remote methods and before and after remote hooks and use that to pass state or get access to the user and token. On Thu, 18 Aug 2016 at 21:03, Pulkit Singhal notifications@github.com wrote:

Here's an idea for those who are still sticking with CLS:

  1. If the following people can share their exact versions for nodejs, npm and the output file called npm-shrinkwrap.json after running npm shrinkwrap then I can look into setting up all of the permutations on Docker and publish a table of results around which ones work and which ones don't.
  2. @Saganus https://github.com/Saganus, @skyserpent https://github.com/skyserpent , @pongstr https://github.com/pongstr , @snowyu https://github.com/snowyu,
    1. Any simplified code (based on your usecases) that you can share for quickly testing if CLS is working, would also be appreciated! I will volume-mount such code into all the docker containers and then maybe use apache-ab or something to test the server. If someone has unit tests, or functional test so that I don't have to build a test harness, even better!
    2. I understand that connectors are a big part of the puzzle so I hope to also test across mongo and memory. If you want me to test additional ones then you may have to help me out a bit by giving me a simple sandbox env that's ready to go. For example, I prepared ones for connectors that I'm familiar with:
  3. mongo - https://github.com/ShoppinPal/loopback-mongo-sandbox/blob/master/docker-compose.yml
  4. elasticsearch - https://github.com/strongloop-community/loopback-connector-elastic-search/blob/master/docker-compose-for-tests.yml

I'm volunteering for running a large-scale experiment to narrow down the issue of what works most easily, therefore, anything you can pitch in to the trial will help.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/strongloop/loopback/issues/1495#issuecomment-240823473, or mute the thread https://github.com/notifications/unsubscribe-auth/ABH2CjIvCZRVQ3Jl5jD2a2IR2zilxWbqks5qhKxrgaJpZM4FQCkF .

Saganus commented 8 years ago

Hi @pulkitsinghal,

Thanks for offering to do this.

Regarding the code I'm not sure I'll be able to extract a small working example since in my case I've only experienced CLS issues when deploying to Heroku. I can however share my npm-shrinkwrap.json with the versions I'm using.

By the way, I was still having issues even after using npm-shrinkwrap, however I was not clearing the cache dir in Heroku so I guess that could've prevented it from working, since after I purged the cache dir and redeployed everything then things seem to started working ok (however not sure for how long or if we'll have the issue again or not).

I think I'm going to do as @digitalsadhu and remove all use of the currentContext and see if we can get rid of the issues.

One question for @digitalsadhu: you say you also got rid of operationHooks. Are they also causing context problems or were they removed for different reasons?

I ask because I also use operationsHooks and I'm not yet aware of any issues but I also am not looking very hard, however you just made me worry a bit :)

Thanks!

Btw, my shrinkwrap file is here:

http://pastebin.com/8vrbcnSU

Thanks for the help and hard work!

digitalsadhu commented 8 years ago

No issues with operation hooks as such it's just that they are afaik the only place where you can't get at the req without current context. In our experience before and after remote hooks are enough. On Fri, 19 Aug 2016 at 16:11, Akram Shehadi notifications@github.com wrote:

Hi @pulkitsinghal https://github.com/pulkitsinghal,

Thanks for offering to do this.

Regarding the code I'm not sure I'll be able to extract a small working example since in my case I've only experienced CLS issues when deploying to Heroku. I can however share my npm-shrinkwrap.json with the versions I'm using.

By the way, I was still having issues even after using npm-shrinkwrap, however I was not clearing the cache dir in Heroku so I guess that could've prevented it from working, since after I purged the cache dir and redeployed everything then things seem to started working ok (however not sure for how long or if we'll have the issue again or not).

I think I'm going to do as @digitalsadhu https://github.com/digitalsadhu and remove all use of the currentContext and see if we can get rid of the issues.

One question for @digitalsadhu https://github.com/digitalsadhu: you say you also got rid of operationHooks. Are they also causing context problems or were they removed for different reasons?

I ask because I also use operationsHooks and I'm not yet aware of any issues but I also am not looking very hard, however you just made me worry a bit :)

Thanks!

Btw, my shrinkwrap file is here:

http://pastebin.com/8vrbcnSU

Thanks for the help and hard work!

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/strongloop/loopback/issues/1495#issuecomment-241028843, or mute the thread https://github.com/notifications/unsubscribe-auth/ABH2CvOCySjvD4aOO93Xv3PNyksvcTMaks5qhbl8gaJpZM4FQCkF .

blackvirus18 commented 8 years ago

Is there any plan or a fix branch for this, this is really frustrating two days before the launch.

josieusa commented 8 years ago

@pulkitsinghal

  1. I can't share any code besides what I published on my account
  2. Here is a test, follow README in order to setup it (beware that this uses cls-hooked instead of continuation-local-storage, and that you'll need to clone my forked repo and checkout the branch cls-hookedand install Node >= 4. https://github.com/josieusa/loopback-context/blob/bf874aa3376b3edc4a6ee64d93855e7420bc71c3/test/main.test.js#L115
  3. In the case of cls-hookedI don't know if it would be useful to test connectors. In fact, it uses async-hook which patches these functions: setTimeout, clearTimeout, setInterval, clearInterval, process.nextTick, promise.prototype.then, as you can see here. https://github.com/AndreasMadsen/async-hook/tree/9c2d1ee927233ec1ad31cbf286029c1c98bd9885/patches In the case of cls-hooked, I think that when it fails it's because they are not patched in time, before the connectors use these functions. That's why I wrote the test the way I did.
bajtos commented 8 years ago

@pulkitsinghal @Saganus @josieusa please open a new issue to discuss this, or perhaps use https://gitter.im/strongloop/loopback instead, so that we can keep this discussion focused on the original proposal, which is to allow an alternative way for injecting context into remote methods, one that does not rely on CLS.

bajtos commented 8 years ago

Hello everybody watching this issue, I have just submitted a patch proposing a solution:

https://github.com/strongloop/loopback/pull/2762

I am encouraging you to take a look and let us know what do you think about my proposal.

snowyu commented 8 years ago

Cons:

  1. The model observe could miss the options If some developer forget to add the options parameter on the remote method which do some CRUD operation.
  2. Not for all build-in remote methods yet.
  3. Need to notify all the component developers to modify their codes.
  4. The written remote method must meet the specification, or crash the server(maybe use the Promise only to solve it). eg,

     Note.remoteMethod('greet', {
       isStatic: true,
       accepts: [
         { arg: 'msg', type: 'string', http: { source: 'query' } },
         { arg: 'options', type: 'object', http: loopback.optionsFromContext },
       ],
       returns: { arg: 'greeting', type: String },
       http: { verb: 'get'}
     });
    
     Note.greet = function(msg, cb) {
       process.nextTick(function() {
         msg = msg || 'world';
         cb(null, 'Hello ' + msg); //crash here. 
       });
     };
    
chris-hydra commented 8 years ago

On Aug 19 @digitalsadhu said:

Fwiw we have stopped using currentContext as well as operation hooks and never looked back. Have yet to come across a situation where this has been a problem for us. (We have a half dozen medium to large loopback apps in production)

@digitalsadhu, could you elaborate on the final form of your approach to replace currentContext?

I see about 6 variations on the solution discussed above, and as a SL noob I would appreciate a simple code sample, a "with CLS" and "no CLS" kind of thing... ; )

chris-hydra commented 8 years ago

Hello again,

I came to this page from: https://docs.strongloop.com/display/public/LB/Using+current+context, which links to this page with the following (emphasis mine):

Refer to loopback issue #1495 updates and an alternative solution.

As I mentioned in my effort to contact @digitalsadhu, I'm trying to sort through all of the "alternative solutions" mentioned above in this thread to understand what the fix will entail.

I see @ritch commented on Sep 17, 2015

Will link the PR when I have something concrete. Thanks for all the feedback.

And @bajtos commented 7 days ago

Hello everybody watching this issue, I have just submitted a patch proposing a solution:

2762

I know everyone's busy and this is OSS, but if the SL docs send me to this page for an alternative solution, which one is it?

As a front-end dev covering for our back-end dev (away on holidays) I'm really reluctant to jump in and start re-inventing yet another fix to this - is there a recommended solution from SL, and if so, where is it?

digitalsadhu commented 8 years ago

@chris-hydra

Our early apps used current context, we fell prey to CLS buggyness and did some rewriting to use a solution similar to what @ritch proposed in the description of this issue. We only did this 1x as we found it just caused us too much hassle trying to pass around the context all the time.

In the end what we settled on was to never use operation hooks which to my knowledge are the only place you can't just get at the context as you please. This was actually a double win for us as operation hooks we find tend to introduce side effects throughout your codebase that can be easy to forget about. (we use remote hooks instead)

So heres how we essentially share our state throughout a request lifecycle.

Set state on the request.

There are various ways you can access the request early enough to set desired state. This sort of thing could be via middleware in server.js for example:

app.use((req, res, next) => {
  req.user.name = 'bob'
  next()
})

Though you can set state in components, mixins and boot scripts. Anywhere you can hook into the request lifecycle.

A boot script or component example

module.exports = function (app) {
  app.remotes().before('**', (ctx, cb) => {
    req.user.name = 'bob'
    cb()
  })
}

A Mixin example

module.exports = function (MyModel) {
  MyModel.beforeRemote('**', (ctx, instance, cb) => {
    req.user.name = 'bob'
    cb()
  })
}

Access state in a remote hook

MyModel.beforeRemote('create', (ctx, instance, cb) => {
  // ctx.req.user.name
  cb()
})

Access state in a remote method

MyModel.myMethod = (ctx, cb) => {
  // ctx.req.user.name
  cb()
}

MyModel.remoteMethod('myMethod', {
  accepts: [
    {arg: 'ctx', type: 'object', http: {source: 'ctx'}}
  ],
  returns: {root: true},
  http: {path: '/my-method', verb: 'get'}
})

I realise that this likely won't accommodate everyones use cases but for us, this has worked out great. We have some pretty decent size loopback apps now and have absolutely zero need for getCurrentContext. Hopefully this helps someone out.

chris-hydra commented 8 years ago

@digitalsadhu, thank you for taking the time to write this up - I hope other folks will get some use out of this as well.

aorlic commented 8 years ago

@digitalsadhu, your approach seems to be natural and simple. How can it be used to access the state from the model's code, for example from the overrided CRUD method?

digitalsadhu commented 8 years ago

@aorlic We typically disable the CRUD endpoint in question and implement our own as a new remote method injecting ctx as needed.

eg. replacing find would look something like this

MyModel.disableRemoteMethod('find', true)

MyModel.customFind = (filter, ctx, cb) => {
  // ctx.req.someState is now available
  MyModel.find(filter, cb)
}

MyModel.remoteMethod('customFind', {
  accepts: [
    {arg: 'filter', type: 'object', http: {source: 'query'}}
    {arg: 'ctx', type: 'object', http: {source: 'ctx'}}
  ],
  returns: {root: true},
  http: {path: '/', verb: 'get'}
})

Please excuse any mistakes, code above is untested and mostly going from memory.

I especially like this approach as the behaviour of the ORM's find method stays the same, only the endpoint is overridden.

aorlic commented 8 years ago

@digitalsadhu, I get it, thank you. Seems like a valid workaround, as the external REST endpoints remain just the same.

However, I just cannot believe that a serious API Server does not permit to legally override a CRUD method (without this kind of hacking) and access the context from it.

What else a developer is expected to do in an API Server, if no to play around with CRUDs?

aorlic commented 8 years ago

@digitalsadhu, one more question for you. In case of "overriding" a findById method this way, what should go here?

http: {path: '/', verb: 'get'}

What does it mean returns: {root: true}?

I can't make it work for this case...

Thank you very much!

digitalsadhu commented 8 years ago

@aorlic looking at the loopback source code, the definition for findById here: https://github.com/strongloop/loopback/blob/master/lib/persisted-model.js#L733 looks like:

{
    description: 'Find a model instance by {{id}} from the data source.',
    accessType: 'READ',
    accepts: [
      { arg: 'id', type: 'any', description: 'Model id', required: true,
        http: { source: 'path' }},
      { arg: 'filter', type: 'object',
        description: 'Filter defining fields and include' },
    ],
    returns: { arg: 'data', type: typeName, root: true },
    http: { verb: 'get', path: '/:id' },
    rest: { after: convertNullToNotFoundError },
}

{root: true} just means that whatever the remote method returns is returned at the root of returned payload.

See the "argument descriptions" section of the remote methods docs here: https://docs.strongloop.com/display/public/LB/Remote+methods

kartsims commented 8 years ago

For those looking for a workaround and details, here is a nice resource on the subject : http://blog.digitopia.com/tokens-sessions-users/

fperks commented 8 years ago

I am struggling to understand, exactly how to utilize any of the above solutions with remote operations like loaded, and access.

We have middleware that extracts a JWT, and in the past has stored data from the jwt in the current context. Then in remote operations such as loaded, or access we do some work, depending on values in the jwt.

getCurrentContext is seemingly randomly returning null now and no amount of shrinkwrap, or reverting library versions seems to have resolved it.

digitalsadhu commented 8 years ago

@fperks can you extract the jwt info and stick it on the req object in your middleware then use beforeRemote and afterRemote hooks instead of access and loaded operation hooks?

fperks commented 8 years ago

@digitalsadhu i feel that would require a significant amount of time and effort given the size of our project. Not to mention the amount of testing and other requirements that we would need to go through again.

I am more looking for a simple and clean solution to this problem.

digitalsadhu commented 8 years ago

@fperks Ah, fair enough. I'm afraid I'm not sure such a solution exists. :(

fabien commented 8 years ago

I've posted my workaround and some observations here: https://github.com/strongloop/loopback/issues/1676

myvo commented 8 years ago

@digitalsadhu Thank you for your suggestion! This is the great solution, we never getCurrentContext and never got currentContext bug. It's really cool.

Just a small updating on step Access state in a remote method

MyModel.remoteMethod('myMethod', {
  accepts: [
    {arg: 'ctx', type: 'object', http: {source: 'req'}}
  ],
  returns: {root: true},
  http: {path: '/my-method', verb: 'get'}
})

http.source can be req or context is also OK. More info http://loopback.io/doc/en/lb2/Remote-methods.html#http-mapping-of-input-arguments

ebarault commented 8 years ago

hi all, there is already a ctx.options object referenced here for operation hooks that

enables hooks to access any options provided by the caller of the specific model method (operation)

also when you look at the ctx context available for exemple at role resolving stage, there is no such ctx.optionsobject, but there is a ctx.remoteContext.options. Anything you put in this later object is then already available in ctx.options in remote method hooks.

Couldn't we not just 'bridge' the ctx.remoteContext.options object with the operation hooks ctx.optionsobject to make the ctx.options behavior consistent all along the workflow ?

This way, one could choose to pass any options in this object and retrieve them in any related piece of code through the app.

One one my regular concern is indeed passing information from custom role resolver to any possible methods and related hooks so that i don't need to do again the role resolving (db requests consuming) in the models when needed for advanced CRUD access control.

It seems @guanbo is trying similar things here by going through the options req argument

Undrium commented 7 years ago

Keep this fresh, we are using the "Loopback Component Group Access"-module to enable group access to our resources. Of course this module requires the getCurrentContext which forced us to downgrade multiple modules to make things work. Such a basic thing shouldn't become an complicated puzzle of module versions to make it work.

ebarault commented 7 years ago

@Undrium please check the solution I managed to set up here, gathering a lot of individual contributions https://github.com/strongloop/loopback/issues/1676#issuecomment-260088138

josieusa commented 7 years ago

Hi everyone, following my comment here on Aug 10, I closed that PR and deleted that branch (in favor of the newer strongloop/loopback-context/pull/11 which works for me and is only waiting for review). EDIT Please use #2728 for comments about it, and not this, thank you

bajtos commented 7 years ago

Hello, the patch allowing remote methods to receive remoting context via options argument has been landed to both master (3.x) and 2.x.

Remaining tasks:

Please note that I am still intending to land (and release) the solution proposed by @josieusa in strongloop/loopback-context#11 too, as it offers an important alternative to our "official" approach.

bajtos commented 7 years ago

Ouch, I pressed "comment" button too soon. The pull request for 2.x is waiting for CI right now (see #3048).

ebarault commented 7 years ago

@bajtos

as it offers an important alternative to our "official" approach

which do you consider as the target approach ?