Search code examples
jsonrestangularfeathersjs

feathersjs modeless service does not return the expected JSON object


Someone can explain me how the service creation works?. After many hours hitting a wall receiving JSON objects than are not what I expect; I came with this code for the before hook, which at least gives me a JSON object and not a 404 error of the previous one. However, as I described before, anything I put in the service creation function is returned back no matter what hook is before or after manipulating the data.

The service code is now this one:

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

  // Initialize our service with any options it requires
  app.use('/v1/login',{
    create(data, params){
      console.log("creating service");
      return Promise.resolve(data); // data or params or whatever is returned to the REST ignoring any modification done via hooks
    }
  });

  // Get our initialized service so that we can register hooks and filters
  const service = app.service('/v1/login');

  service.hooks(hooks);

  if (service.filter) {
    service.filter(filters);
  }
};

The hooks file is the same and the after hook (login) is now this:

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) {
    if (hook.type !== 'before') {
      throw new errors.Unprocessable('This hook should only be used as before hook.');
    }
    const sequelizeClient = hook.app.get('sequelizeClient');
    sequelizeClient.query('SELECT to_jsonb(login_user($app::varchar, $usr::varchar, $pwd::varchar)) resultcode',
      { bind: { app: hook.data.app, usr: hook.data.usr, pwd: hook.data.pwd },
        type: sequelizeClient.QueryTypes.SELECT }
    ).then(login => {
      if (login[0].resultcode.loginStatus !== 0){
        throw new errors.NotAuthenticated(login[0].resultcode.message);
      }else if(login[0].resultcode.sessionId !== null){
        delete hook.data.usr;
        delete hook.data.pwd;
        hook.data.resultcode = login[0].resultcode;
        var payload = { 'iss': 'feathers',
          'sub': hook.data.resultcode.sessionId,
          'aud': 'localhost',
          'rol': hook.data.resultcode.rol,
          'nbf': moment(),
          'iet': moment(),
          'jti': hook.data.resultcode.nonce
        };
        var opts = {
          name: 'jwt',
          entity: 'sessions',
          service: 'sessions',
          passReqToCallback: true,              
          session: false
        };
        opts.jwtFromRequest = [ExtractJwt.fromAuthHeader(),
          ExtractJwt.fromAuthHeaderWithScheme('Bearer'),
          ExtractJwt.fromBodyField('body')
        ];
        opts.secret = hook.data.resultcode.message;
        hook.app.passport.createJWT(payload, opts)
        .then( jwtResult => {
          hook.result = { "token": jwtResult };
          return hook;
        })
        .catch(error => {
          console.log("JWT-ERROR: "+error);
          throw new errors.GeneralError('Error generating jwt. Login failed');
        });
      }else{
        console.log("ERROR: No session created");
        throw new errors.GeneralError('Session cannot be created');
      }
    }).catch(error => {
      console.log("ERROR: "+error);
      throw new errors.GeneralError('Database error');
    });
  };
};

As I said, what is returned is the params received in the service creation method. Is there a way to rid off the service creation second param?, I tried, but then an error expecting a middleware function is raised among others. What I'm doing wrong or I misunderstood about the hooks (I thought that they manipulates data and params before they are returned back to the REST client, but seemed that it is not that way. Please explain. I'm very new using feathers. I expected to achieve what is written in the page 161 "Pro Tip: hook.result Can also be set in a before hook which will skip the service method call (but run all other hooks).", but no luck until now. Is there any other way or condition that I missed?

UPDATE, no luck yet

I saw the authentication service code and I try to do something similar, they actually returns the JWT promise which creates the token and inside returns the token inside a JSON, so I put all the process inside the service creation (with or without hooks the same result NOTHING is returned

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

const hooks = require('./login.hooks'); //optional the only hook on create is to validate that params user and password are present
const filters = require('./login.filters');

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

  // Initialize our service with any options it requires
  app.use('/v1/login',{
    create(data, params){
      console.log("creating service");
      const sequelizeClient = app.get('sequelizeClient');
      var modelSession = sequelizeClient.query('SELECT to_jsonb(login_user($app::varchar, $usr::varchar, $pwd::varchar)) resultcode',
        { bind: { app: data.app, usr: data.usr, pwd: data.pwd, cte: params.headers },
          type: sequelizeClient.QueryTypes.SELECT }
      ).then(login => {
        if (login[0].resultcode.login_status !== 0){
          throw new errors.NotAuthenticated(login[0].resultcode.pmessage);
        }else if(login[0].resultcode.sessionId !== null){
          delete data.usr;
          delete data.pwd;
          data.resultcode = login[0].resultcode;
          var payload = { 'iss': 'feathers',
            'sub': data.resultcode.sessionId,
            'aud': 'localhost',
            'rol': data.resultcode.rol,
            'nbf': moment(),
            'iet': moment(),
            'jti': data.resultcode.nonce
          };
          var opts = {
            name: 'jwt',
            entity: 'sessions',
            service: 'sessions',
            passReqToCallback: false, // to avoid verification                
            session: false
          };
          opts.jwtFromRequest = [ExtractJwt.fromAuthHeader(),
            ExtractJwt.fromAuthHeaderWithScheme('Bearer'),
            ExtractJwt.fromBodyField('body')
          ];
          opts.secret = 'secret';
          return app.passport.createJWT(pload, opts)
          .then( function(jwtResult) { // mimic of authentication service, but less sophisticated
            delete data.resultcode;
            return { token: jwtResult }; // expected result, but nothing is received
          })
          .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');
      });
    }
  });

  // Get our initialized service so that we can register hooks and filters
  const service = app.service('/v1/login');

  service.hooks(hooks); //This line can be commented out

  if (service.filter) {
    service.filter(filters);
  }
};

The hooks are this

module.exports = {
  before: {
    all: [],
    find: [ disallow() ],
    get: [ disallow() ],
    create: [ validateLoginParams() ], // just to see if the params are truthy
    update: [ disallow(), disableMultiItemChange() ],
    patch: [ disallow(), disableMultiItemChange() ],
    remove: [ disallow(), disableMultiItemChange() ]
  },
...  //All is as in default, so empty brackets ;-)
}

The project was created using feathers-cli and feathers Auk, this is the relevant package json

  "dependencies": {
    "body-parser": "^1.17.2",
    "compression": "^1.6.2",
    "cors": "^2.8.3",
    "feathers": "^2.1.2",
    "feathers-authentication": "^1.2.3",
    "feathers-authentication-hooks": "^0.1.3",
    "feathers-authentication-jwt": "^0.3.1",
    "feathers-authentication-local": "^0.3.4",
    "feathers-authentication-oauth2": "^0.2.4",
    "feathers-configuration": "^0.4.1",
    "feathers-errors": "^2.8.0",
    "feathers-hooks": "^2.0.1",
    "feathers-hooks-common": "^3.3.2",
    "feathers-rest": "^1.7.2",
    "feathers-sequelize": "^2.0.0",
    "feathers-socketio": "^1.6.0",
    "helmet": "^3.6.0", ...

I'm working with feathersjs two/three weeks, to start a new project and seemed a good REST JavaScript framework, I would like to see it using TypeScript as in angular2 in future releases (much clearer code for me). I hope to see a solution soon for this problem or someone that point me out where I commit a mistake or if I definitively did not understand how to use feathers or for what is intended.


Solution

  • After seeing closely the authentication code in feathers and how other services were created using sequelize, I realized that the problem from the beginning was to try to do a new service without using any model. I read this two googled pages https://github.com/feathersjs/feathers-hooks/issues/25 and https://github.com/feathersjs/feathers/issues/351, but the mentioned solution of both lead to a non existing page. The real problem is how to do a modeless service, or how to create a service that returns a resource not present in any backend;in other words, how to return a resource that is generated through the service itself.

    Answer: no way, unless you create a service from scratch. Said that, my only way was to use the built-in authentication service, but if you need to do other things that are modeless (not authentication, but email registration, or a result from a process that is done in the service or directly from database, but no CRUD), you have to ask to feathersjs' developers, (please help, I'll need soon other non-CRUD services).

    At the end I just eliminate the default hook, and the authentication service code changed is this:

      // to create a new valid JWT (e.g. local or oauth2)
      app.service('/v1/authentication').hooks({
        before: {
          create: [
            // authentication.hooks.authenticate(config.strategies),
            validateLoginParams(),
            login()
          ],
          remove: [
            authentication.hooks.authenticate('jwt')
    

    ...

    and the login hook is the whole code which returns the hook with the result and I get the access token JSON back. Now my problem is with the generated token which does not have the payload I set, but only those parts that are not present in the authentication configuration.

    Thanks for your time and patience reading this code story. Hoping this help to other newbies.