Params In Connection In Preprocessor

31 views
Skip to first unread message

Paul Tiseo

unread,
Apr 30, 2015, 5:29:44 PM4/30/15
to action...@googlegroups.com
So, in v10.1.2, I have an initializer that sets up a preprocessor that adds data into connection.params. That data can be pulled from the connection in subsequent downstream preprocessors, but not in an action's run(). Is that normal behavior?

Evan Tahler

unread,
May 1, 2015, 5:28:39 AM5/1/15
to action...@googlegroups.com
Problems like this was one of the main things fixed in v11. 

Basically, in some places you can modify the connection directly, say `connection.stuff = 'awesome'` and you can access that property directly (mainly middleware and servers).  However, in actions, we were "overly safe" about saving the state of the connection (to prevent problems with parallel actions overwriting each other's params), which led to the pattern of needing to access `connection.originalConnection.stuff` within actions. 

But, this is all much better now in V11.  `data.connection.stuff` is uniformly available in both actions and middleware.  

Paul Tiseo

unread,
May 1, 2015, 8:32:32 AM5/1/15
to action...@googlegroups.com

Ok, so I am in the process of looking at migrating my server to v11, and was wondering about:
- Is there a migration doc?
- Do I just have to worry about my actions and pre/post processors signatures? Or, should I create a new server from scratch and carefully transfer actions and initializers, etc.? IOW, are there changes to files that I wouldn't normally look at, but aren't the same as in v10?
- Also, one of my preprocessors used the template param in api.actions.addPreProcessor(function(connection,template, next){}). The template allowed me to add a property to each action called requiredAuth and check it in the preProcessor. Now, the signature for a preProcessor is function(data, next), where data does not seem to contain template? How do I get to the properties of the action?

Evan Tahler

unread,
May 2, 2015, 6:37:47 AM5/2/15
to action...@googlegroups.com
The best place to look for the changes (that I know of) is here: https://github.com/evantahler/actionhero/releases/tag/v11.0.0
To see the changes in an actual project, you can see how we upgraded the demo: https://github.com/evantahler/actionhero-tutorial/pull/13/files

I tend to just upgrade a project in-place, but you can make a new one if you like.

in middleware, `data. actionTemplate ` is available to you.  You can keep doing the same thing! 

Paul Tiseo

unread,
May 7, 2015, 3:27:47 PM5/7/15
to action...@googlegroups.com
Either I am doing something wrong or it isn't fixed. :(

Here's the preprocessor, authVerifier, being setup in an initializer. Line 53 takes the properties and values in the JWT token and assigns then to data.params.

var fs = require('fs');
var path = require('path');
var jwt = require('jsonwebtoken');

module.exports = {

  loadPriority
:  1500,
  startPriority
: 1500,
  stopPriority
:  1000,

  initialize
: function(api, next) {
    api
.auth = {};

   
// if the encryption method is RS or ES, then we need key files, else just use api.config.auth.secretOrPrivateKey
   
if(api.config.auth.tokenAlgorithm.indexOf('HS') != 0) {
      api
.auth.privateKey = fs.readFileSync(path.join(__dirname, '../', api.config.auth.secretOrPrivateKey));
      api
.auth.publicKey  = fs.readFileSync(path.join(__dirname, '../', api.config.auth.publicKey));
   
}
   
else {
      api
.auth.privateKey = api.config.auth.secretOrPrivateKey;
      api
.auth.publicKey  = api.config.auth.secretOrPrivateKey;
   
}
   
   
// TODO: load and confirm the configured providers in /config/auth.js
   
//       check if there is a protocol, and if it is a known type, and then
   
//       if the properties are defined and valid for each protocol

   
// decrypts and verifies the token
    api
.auth.isAuthenticated = function(data) {
     
var headers = data.connection.rawConnection.req.headers;

     
// verify if we have an Authorization header and it contains a Bearer token
     
if( (!headers.authorization) || (headers.authorization.indexOf('Bearer') != 0) ) {
       
return 'No proper authorization header found.';
     
}

     
var payload;
     
try {
        payload
= jwt.verify(headers.authorization.substring(7), api.auth.publicKey, {issuer: api.config.auth.issuer});
     
}
     
catch(err) {
       
if(err instanceof jwt.TokenExpiredError) {
         
return 'Token expired.';
       
}
       
else if(err instanceof jwt.JsonWebTokenError) {
         
return 'Bad token (' + err.message + ').';
       
}
       
else {
         
return 'Token cannot be verified.';
       
}
       
return 'Unknown JWT error.';
     
}
      api
.lodash.assign(data.params, payload);
     
return null;
   
}

   
var authVerifier = {
      name
: 'authVerifier',
     
global: false,
      priority
: 1500,
      preProcessor
: function(data, next) {
       
var err = null;
        api
.log('authVerifier::data.params','info',data.params);
       
// check if we already authenticated; basically do we have a valid, non-expired token
       
var result = api.auth.isAuthenticated(data);
       
if (result === null) {
         
return next(err);
       
}
       
// else indicate user must authenticate
        err
= new Error('Please authenticate before accessing this function: ' + result);
       
return next(err);
     
}
   
};
    api
.actions.addMiddleware(authVerifier);

   
next();
 
},

  start
: function(api, next) {
   
next();
 
},

  stop
: function(api, next) {
   
next();
 
}

}

Here's a subsequent preprocessor, called rbacChecker, for authorizations. When the code runs, it properly outputs the values picked up in the above preprocessor.

module.exports = {

  loadPriority
:  1550,
  startPriority
: 1550,
  stopPriority
:  1000,

  initialize
: function(api, next) {

   
// add a preprocessor that checks route used vs. permissions in DB
   
var rbacChecker = {
      name
: 'rbacChecker',
     
global: false,
      priority
: 1550,
      preProcessor
: function(data, next) {
       
var err = null;
        api
.log('rbacChecker::data.params','info',data.params);
       
// token parser should have put user id in connection.params; use it to check authorization for endpoint
        api
.models.operator.find({
           
where: { id: data.connection.params.id, isLocked: false, $or: [ {deletedAt: {$gt: (new Date())}}, {deletedAt: null} ] },
            include
: [
             
{ model: api.models.permission,
                include
: [
                 
{ model: api.models.activity,
                    include
: [
                     
{ model: api.models.target, where: { name: data.connection.rawConnection.parsedURL.pathname } },
                     
{ model: api.models.operation, where: { verb: data.connection.rawConnection.method } }
                   
]
                 
}
               
]
             
}
           
]
       
})
       
.then(function(operator) {
         
if(!!operator) {
           
// approved
           
next(err);
         
}
         
else {
           
// not approved
            api
.log('User was not found as approved for action.', 'info', { id: data.connection.params.id, name: data.connection.rawConnection.parsedURL.pathname, verb: data.connection.rawConnection.method } );
            err
= new Error('You are not authorized for this action.');
           
next(err);
         
}
       
})
       
.catch(function(e) {
         
// error (hence not approved)
          api
.log(e, 'error', { id: data.connection.params.id, name: data.connection.rawConnection.parsedURL.pathname, verb: data.connection.rawConnection.method } );
          err
= new Error('You are not authorized for this action.');
         
next(err);
       
});
     
}
   
};
    api
.actions.addMiddleware(rbacChecker);

   
next();

 
},

  start
: function(api, next) {
   
next();
 
},

  stop
: function(api, next) {
   
next();
 
}

}

However, once I get to the action, those params are gone.

var massive = require("massive");

exports.action = {
  name
:                   'getWorkOrders',
  description
:            'getWorkOrders',
  middleware
:             ['authVerifier','rbacChecker'],
  blockedConnectionTypes
: [],
  outputExample
:          {},
  matchExtensionMimeType
: false,
  version
:                1.0,
  toDocument
:             true,

  inputs
: {},

  run
: function(api, data, next) {
   
var err = null;
    api
.log('getWorkOrders::data.params', 'info', data.params);

   
return next(err);
 
}
};

Here's an example output with a valid JWT:

2015-05-07 15:22:20 - info: authVerifier::data.params action=getWorkOrders, apiVersion=1
2015-05-07 15:22:20 - info: rbacChecker::data.params action=getWorkOrders, apiVersion=1, first_name=Super, last_name=Admin, email=superadmin@commercialservices.com, id=1, iat=1430934095, exp=1431538895, iss=AEX-Server
2015-05-07 15:22:20 - info: getWorkOrders::data.params action=getWorkOrders, apiVersion=1

Am I missing something?



On Friday, May 1, 2015 at 5:28:39 AM UTC-4, Evan Tahler wrote:

Evan Tahler

unread,
May 8, 2015, 8:46:34 AM5/8/15
to action...@googlegroups.com, paulx...@gmail.com
What's going on here is the order of param sanitization.  PreProcessors have access to raw params (anything a user might POST), however actions strip out any params that aren't in that action's inputs.

Either add the params you expect to the action or disable this param "scrubbing"

Paul Tiseo

unread,
May 8, 2015, 1:26:30 PM5/8/15
to action...@googlegroups.com, paulx...@gmail.com
Ah, that makes sense.

What I elected to do was create a new property, data.token, and put the data there rather than params, so that I could keep the param whitelisting on.
...
Reply all
Reply to author
Forward
0 new messages