1

I want to implement a simple "published" field for the documents in database.

For example, if an user is not authenticated, I want to always insert { published: true } in the query for all find/get request, so it will return only published documents (useful in front end)

But if the user is authenticated and have required roles, I want to return all documents, not only published (useful in admin).

So I'm trying to create a before hook:

// assignQuery.hook.js

const { some } = require('lodash');

const defaultOptions = {
  rolesField: 'roles',
  query: {},
  allowRoles: []
};
module.exports = function (options) {
  return async context => {

    options = Object.assign({}, defaultOptions, options);

    // If it was an internal call then skip this hook
    if (!context.params.provider) {
      return context;
    }

    const { user } = context.params;

    // Return unchanged context if user have some "allowRoles"
    if (user && options.allowRoles && options.allowRoles.length && some(options.allowRoles, (role) => {
      return (user[options.rolesField] || '').includes(role);
    })) return context;

    // else assign query filter
    context.params.query = Object.assign({}, context.params.query, options.query);

    return context;
  };
};

// service.hooks.js

module.exports = {
  before: {
    all: [],
    find: [
      assignQuery({ query: {published:true}, allowRoles: ['admin', 'edit'] }),
    ],
    get: [
      assignQuery({ query: {published:true}, allowRoles: ['admin', 'edit'] }),
    ],
    create: [ 
      authenticate('jwt'), 
      restrictToRoles({ roles: ['admin', 'edit']}),
    ],
    update: [ 
      authenticate('jwt'), 
      restrictToRoles({ roles: ['admin', 'edit']}),})
    ],
    patch: [ 
      authenticate('jwt'), 
      restrictToRoles({ roles: ['admin', 'edit']}),
    ],
    remove: [ 
      authenticate('jwt'),
      restrictToRoles({ roles: ['admin', 'edit']})
    ]
  },

};

The problem is I have to authenticate user to get his roles, so if user is not authenticated, the service will send an Unauthorized error, in place of allow the response with the filtered query.

So I have to call authenticate before assignQuery. In this case it will work fine for admin, but unauthorized users in front-end cannot retrieve published articles.

And if I don't authenticate before assignQuery, it's working fine in front-end, unauthorized users can see only published articles, but it's not working anymore in the admin, administrators will always receive only published articles too, because in the assignQuery hook, context.params.user is undefined (because we don't authenticate before...)

So I need a way to check if user is authenticated, but not send him an Unauthorized error if he is unauthorized, I just want to send the response of the modified query.

I think I need something like this:

before: {
    all: [],
    find: [
      unless(isAuthenticateAndHaveRoles({roles:['admin', 'edit']}), 
        assignQuery({ query: {published:true} })
      )
    ],
    ...

But I don't know how to make a "no-blocking" authenticate or an isAuthenticate hook. Is it possible?

I hope my explanation is not so confuse, the goal is just to implement a published/draft (visible/hidden) (online/offline) field for my documents.

Any help is welcome. Thank you so much!

divibisan
  • 11,659
  • 11
  • 40
  • 58
Molosc
  • 33
  • 4

1 Answers1

2

For Feathers v4 and later, you can use the anonymous authentication strategy.

For v3 and earlier, the authenticate hook has a currently undocumented allowUnauthenticated option which, if set to true, just ads a params.authenticated flag. So

find: [
  authenticate('jwt', {
    allowUnauthenticated: true
  }),
  assignQuery({ query: {published:true}, allowRoles: ['admin', 'edit'] }),
]

Should do it.

Daff
  • 43,734
  • 9
  • 106
  • 120
  • Great, exactly what I was looking for. Thank you so much Daff! – Molosc Aug 16 '18 at 22:15
  • @Molosc If this solved the problem, upvote the answer and accept it by clicking the green checkmark under the voting buttons – divibisan Aug 16 '18 at 22:23
  • @Daff Do you perhaps know if there is an predicate which i can use with feathers-hooks-common? something like this `iffElse(isAuthenticated(),[/*authenticated hooks here*/],[/*unauthenticated hooks here*/])` – Ismail Dec 15 '18 at 17:57
  • Nevermind you could write an predicate with `return context.params.authenticated` – Ismail Dec 15 '18 at 18:09
  • Hi @Daff I'm using feathersjs ts (typescript) version. and in that, When I'm defining ``` authenticate('jwt', { allowUnauthenticated: true }) ``` I'm getting `Argument of type '{ allowUnauthenticated: boolean; }' is not assignable to parameter of type 'string'.` error. Please help me to fix this issue. – Jaideep Ghosh Jan 03 '20 at 13:18
  • I updated the answer. In v4 and later you should use [anonymous authentication](https://docs.feathersjs.com/cookbook/authentication/anonymous.html). – Daff Jan 03 '20 at 16:31