0

I'm a newbie using feathersjs, and it is very flexible and seemed to be very productive for simple CRUD use cases. I've been having issues trying to customize feathers' authorization. The example in the documentation for customizing the login response shows here.

app.service('/authentication').hooks({
  after: {
    create: [
      hook => {
        hook.result.foo = 'bar';
      }
    ]
  }
});

works as expected, the JSON received has the access token and the foo property. However, if I modified it adding a hook function like this:

app.service('/authentication').hooks({
  after: {
    create: [
      hook => {
        hook.result.foo = 'bar';
      },
      login()
    ]
  }
});

or just

after:{
  create: [ 
    login()
  ] ...

or set it in the before, it doesn't matter the new property is not set. the login hook code is:

const errors = require('feathers-errors');
const jwt = require('feathers-authentication-jwt');
const ExtractJwt = require('passport-jwt').ExtractJwt;
const moment = require('moment');

module.exports = function () {
  return function (hook) {
    // call a function that validates the username and password and returns a session id, rol and other information to fillout the payload
    const sequelizeClient = hook.app.get('sequelizeClient');
    sequelizeClient.query('SELECT ... ) // Sequelize magic
    .then(mySession => { // session successfully created
        hook.data.resultcode = mySession[0].resultcode;
        delete hook.data.usr;
        delete hook.data.pwd;
        //payload I want, but simply doesn't go or is partially taken
        var pload = { header: { typ: 'access' },
          strategy: 'jwt',
          'issuer': 'feathers',
          'subject': hook.data.resultcode.session_id,
          'audience': 'localhost',
          'rol': hook.data.resultcode.rol,
          'nbf': moment(),
          'iet': moment()
        };
        var opts = {
          name: 'jwt', 
          entity: 'sessions', 
          service: 'sessions',
          passReqToCallback: false, 
          session: false // whether to use sessions,
          //Verifier: Verifier
        };
        opts.jwtFromRequest = [ExtractJwt.fromAuthHeader(),
          ExtractJwt.fromAuthHeaderWithScheme('Bearer'),
          ExtractJwt.fromBodyField('body')
        ];
        opts.secret = 'secret';
        hook.data.payload = pload; // as documentation says to modify the payload, but does not work
        hook.params.payload = pload;
        hook.app.passport.createJWT(pload, opts)
        .then( jwtResult => {
          hook.result.token = jwtResult;
          console.log("after hook result:"+ JSON.stringify(hook.result));
          return hook;
        })
        .catch(error => {
          console.log("JWT-ERROR: "+error);
          throw new errors.GeneralError('Error generating jwt. Login failed');
        });
    }).catch(error => {
      console.log("ERROR: "+error);
      throw new errors.GeneralError('Database error');
    });
  };
};

This code runs, the session is created in the database, all good, but the response goes to the client before the new token is created and shown in the console. the foo is there, but not the new token property added in the hook.result object. The difference is that foo is synchronically added, the token property is asynchronously set. I read this answer (Understanding FeathersJS hooks), but for a the code generated with feathers-cli I don't get how to do it, and if it applies for Auk (the feathers version I'm using.) Now my questions:

  1. How am I able to send the token property when it is generated asynchronously?
  2. How can I change/override the payload, the secret and the options of the token. The secret in fact will be by session, not unique or system-wide as it is in the configuration. The system wide-one maybe are going to be set as a public key if the token is encrypted using RSA for instance.
  3. Where is an example of how to create a custom service (not based on sequelize, memory, or other backend) if the resource is calculated by the REST server itself?. Would be good to have an example in the advanced section of the docs.

Thanks, and sorry if the answer is obvious, but I haven't reach the aha! moment with feathersjs for cases as the third and first question is about.

George
  • 35
  • 1
  • 10

1 Answers1

0

finally I could do it and I answer also another post I published some days ago.

I created another service called session and inside the service creation code I put this:

module.exports = function () {
const app = this;

// Initialize our service with any options it requires
app.use('/v1/session', {
  create(data, params){
    console.log("creating service");
    const sequelizeClient = app.get('sequelizeClient');
    return sequelizeClient.query('SELECT ... //Sequelize magic must be returned its promise
    ).then(login => {
        delete data.usr;
        delete data.pwd;
        data.resultcode = login[0].resultcode;
        var payload = { 'iss': 'feathers',
          'sub': data.resultcode.session_id,
          'aud': 'localhost',
          'nbf': moment(),
          'iet': moment()
        };
        var opts = {
          name: 'jwt',
          entity: 'sessions',
          service: 'sessions',
          passReqToCallback: false,
          session: false
        };
        opts.jwtFromRequest = [ExtractJwt.fromAuthHeader(),
          ExtractJwt.fromAuthHeaderWithScheme('Bearer'),
          ExtractJwt.fromBodyField('body')
        ];
        opts.secret = 'secret';
        return app.passport.createJWT(pload, opts) // Here is the correction return the nested promise
        .then( function(jwtResult) {
          delete data.resultcode;
          data.token = jwtResult;
          return Promise.resolve(data); // and return a promise with the desired result, which can be any object, not necessarily data or params
        })
        .catch(error => {
          console.log("JWT-ERROR: "+error);
          throw new errors.GeneralError('Error generating jwt. Login failed');
        });
      }
    }).catch(error => {
      console.log("ERROR: "+error);
      throw new errors.GeneralError('Database error');
    });
  }
});

Due to the asynchronous nature of sequelize query and the createJWT functions, the response was always whatever I set in the creation return. So, I realized to return each nested promise (the clue came from an example where the createJWT is returned in an authentication example), but I was confused by the sequelize one. At the end was an newbie mistake handling promises in feathers. With this answer, my other post was also answered.

Thanks.

George
  • 35
  • 1
  • 10