GithubHelp home page GithubHelp logo

Comments (27)

fratzinger avatar fratzinger commented on August 22, 2024

hey,
I've got no working example. Which version of feathers do you use? Is it generated by @feathersjs/cli or how do you organize it? If you like, share your authentication.js or just some parts.

from feathers-casl.

alxblog avatar alxblog commented on August 22, 2024

Thank you for your reply (it's my first issue request on Github).
I use Feathers 4.5.11, I tried to set Feathers-casl up on a stock and fresh CLI generated app (with NeDB).

So the basic authentication.js file is

const { AuthenticationService, JWTStrategy } = require('@feathersjs/authentication');
const { LocalStrategy } = require('@feathersjs/authentication-local');
const { expressOauth } = require('@feathersjs/authentication-oauth');

module.exports = app => {
  const authentication = new AuthenticationService(app);

  authentication.register('jwt', new JWTStrategy());
  authentication.register('local', new LocalStrategy());

  app.use('/authentication', authentication);
  app.configure(expressOauth());
};

If, in the same file, I register "authentication" service and hooks that lets trig authentication.hooks.js

const { AuthenticationService, JWTStrategy } = require('@feathersjs/authentication');
const { LocalStrategy } = require('@feathersjs/authentication-local');
const { expressOauth } = require('@feathersjs/authentication-oauth');
const hooks = require('./services/authentication/authentication.hooks'); // require authentication.hooks

module.exports = app => {
  const authentication = new AuthenticationService(app);

  authentication.register('jwt', new JWTStrategy());
  authentication.register('local', new LocalStrategy());

  app.use('/authentication', authentication);
  app.service('authentication').hooks(hooks); //register authentication service and hooks
  app.configure(expressOauth());
};

is that the good way to do it ?

from feathers-casl.

fratzinger avatar fratzinger commented on August 22, 2024

Yep, that's definitely a way.
You should have at least a folder with src/services/users containing users.service.js and users.hooks.js. Since the authentication is just another service (like all the other services), I handle it the exact same way.
Your authentication.js file is pretty much the same as users.service.js, so I renamed the authentication.js file in authentication.service.js and followed the same way as in users.service.js to register the hooks.

After this setup your after:create hook in authentication.hooks.js does not get called? If so, what does your authentication.hooks.js look like?

from feathers-casl.

alxblog avatar alxblog commented on August 22, 2024

So, I renamed (just to follow your logic) ./src/authentication.js in authentication.service.js and move it to ./src/services/authentication/
Authentication service hooks are now called. ability and rules props are present in POST authentication response.

authentication.abilities.js and authentication.hooks.js are exactly de same as your example (copy/past) in your "Getting Started" section.

the only last thing I did is to add authorize() hook to tasks.hooks.js

const { authenticate } = require('@feathersjs/authentication').hooks;
const { authorize } = require('feathers-casl').hooks;

module.exports = {
  before: {
    all: [authenticate('jwt')],
    find: [
      authorize({ checkAbilityForInternal: true }) // make sure this hook runs always last
    ],
    get: [
      authorize({ checkAbilityForInternal: true }) // make sure this hook runs always last
    ],
    create: [
      authorize({ checkAbilityForInternal: true }) // make sure this hook runs always last
    ],
    update: [
      authorize({ checkAbilityForInternal: true }) // make sure this hook runs always last
    ],
    patch: [
      authorize({ checkAbilityForInternal: true }) // make sure this hook runs always last
    ],
    remove: [
      authorize({ checkAbilityForInternal: true }) // make sure this hook runs always last
    ]
  },

  after: {
    all: [
      authorize({ checkAbilityForInternal: true }), // make sure this hook runs always first
    ],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  },

  error: {
    all: [],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  }
};

from feathers-casl.

fratzinger avatar fratzinger commented on August 22, 2024

So is there anything left?^^

from feathers-casl.

fratzinger avatar fratzinger commented on August 22, 2024

If not, you could close the issue then. I won't add an example for now, but will keep it in my mind for later.

Do you know of the feathers slack group? It's a better place for general questions. http://slack.feathersjs.com/ :)

from feathers-casl.

alxblog avatar alxblog commented on August 22, 2024

Maybe it's a misunderstanding of CASL, but if I do a GET/READ (find) request on tasks service, I still have all tasks. not only tasks owned by the current authenticated user (use id : 75RYYlBKLp2HyAeF) .

{
    "total": 2,
    "limit": 10,
    "skip": 0,
    "data": [
        {
            "text": "my task 1",
            "userId": "75RYYlBKLp2HyAeF",
            "_id": "3ZLQN4eiLCQzsPjk"
        },
        {
            "text": "my task 2",
            "userId": "foobar",
            "_id": "fwracLtabKpi2GX2"
        }
    ]
}

Should it not be "filtered" by

can('manage', 'tasks', { userId: user.id });

PS: Thank you for the feathers slack channel.

from feathers-casl.

fratzinger avatar fratzinger commented on August 22, 2024

Please post all your rules and what is the output of console.log(user.id)?
In a hook right before the authorize()-hook, what is the output of console.log(context.params.user) and console.log(context.params.ability)?

from feathers-casl.

alxblog avatar alxblog commented on August 22, 2024

Here is the authentication.abilites.js

const { AbilityBuilder, createAliasResolver, makeAbilityFromRules } = require('feathers-casl');

// don't forget this, as `read` is used internally
const resolveAction = createAliasResolver({
	update: 'patch',       // define the same rules for update & patch
	read: ['get', 'find'], // use 'read' as a equivalent for 'get' & 'find'
	delete: 'remove'       // use 'delete' or 'remove'
});

const defineRulesFor = (user) => {
	// also see https://casl.js.org/v5/en/guide/define-rules
	const { can, cannot, rules } = new AbilityBuilder();

	console.log(user.id) //debug purpose

	if (user.role && user.role.name === 'SuperAdmin') {
		// SuperAdmin can do evil
		can('manage', 'all');
		return rules;
	}

	if (user.role && user.role.name === 'Admin') {
		can('create', 'users');
	}

	can('read', 'users');
	can('update', 'users', { id: user.id });
	cannot('update', 'users', ['roleId'], { id: user.id });
	cannot('delete', 'users', { id: user.id });

	can('manage', 'tasks', { userId: user.id });
	can('create-multi', 'posts', { userId: user.id })

	return rules;
};

const defineAbilitiesFor = (user) => {
	const rules = defineRulesFor(user);

	return makeAbilityFromRules(rules, { resolveAction });
};

module.exports = {
	defineRulesFor,
	defineAbilitiesFor
};

console.log(user.id) returned undefined

and in tasks.hooks.js

const { authenticate } = require('@feathersjs/authentication').hooks;
const { authorize } = require('feathers-casl').hooks;

module.exports = {
  before: {
    all: [authenticate('jwt')],
    find: [
      (context) => {
        console.log(context.params.user) //debug purpose
        console.log(context.params.ability) //debug purpose
      },
      authorize({
        checkAbilityForInternal: true,
      }) // make sure this hook runs always last
    ],
//[...]

context.params.user returned

{
  email: '[email protected]',
  password: '$2a$10$tTeAjaao60c0f7n1zIrlOubu6pNpRnckYFI37drSOmjYebVmfkrte',
  firstname: 'Alex',
  lastname: 'Auss',
  active: true,
  _id: '75RYYlBKLp2HyAeF'
}

and context.params.user returned undefined

I just publish a repo with my files :
https://github.com/alxblog/feathers-casl-example

from feathers-casl.

fratzinger avatar fratzinger commented on August 22, 2024

multiple problems here:

  1. you use _id, not id
  2. if context.params.ability is undefined, so authorize() will be skipped at all
    Please set some breakpoints here and there and see how it goes. First of all check the hook in authentication.hooks.js, then your authentication.abilities.js.

from feathers-casl.

fratzinger avatar fratzinger commented on August 22, 2024

did you find the error? What was the problem?
If there's anything left, let me know.

from feathers-casl.

dasantonym avatar dasantonym commented on August 22, 2024

I'd also be interested in solving this. I have the same problem, although I am authenticating with Auth0 tokens and apparently authentication.hooks.js never gets called again after I initially authenticated. Every subsequent request only contains an access token that gets validated and then the appropriate user object is added to params. This way the abilities remain unset, except for the initial authentication.

I was under the impression I am just using this wrong somehow. Is the create hook in authentication.hooks.js meant to be invoked on every request to add abilities or couldn't I just add the abilities in an extra hook after authenticate('auth0')? In the latter case, I'd need to add this for every service using CASL which is not mentioned in the docs.

from feathers-casl.

dasantonym avatar dasantonym commented on August 22, 2024

FYI, if I add this in my service's before hook, it works:

all: [
  authenticate('auth0'),
  context => {
    const { user } = context.params
    if (user) context.params.ability = defineAbilitiesFor(user)
    return context
  }
]

from feathers-casl.

J3m5 avatar J3m5 commented on August 22, 2024

This is what is recommended to do in the docs
But in the after hook of the authentication service.

feathers-casl by default looks for context.params.ability in the authorize-hook and connection.ability in the channels.
You want to authorize users who are authenticated first with @feathers/authentication.
We can add hooks to the /authentication service to populate things to context.params and connection under the hood.
We use this here to put ability on these objects, which makes it available to all hooks after the authenticate(...)-hook.
This way we can define rules in just one place:

// src/services/authentication/authentication.hooks.js
const { defineAbilitiesFor } = require('./abilities');

module.exports = {
  after: {
    create: [
      context => {
        const { user } = context.result;
        if (!user) return context;
        const ability = defineAbilitiesFor(user);
        context.result.ability = ability;
        context.result.rules = ability.rules;

        return context;
      }
    ],
  },
};

from feathers-casl.

dasantonym avatar dasantonym commented on August 22, 2024

Yes, but this hook is never called during a request for a resource from a service other than authentication. Thus, the abilities prop stays undefined.

from feathers-casl.

dasantonym avatar dasantonym commented on August 22, 2024

Maybe this is specific to my authentication strategy and other strategies are using the create hook of authentication? Not sure if the create hook is meant to be called on every request.

from feathers-casl.

fratzinger avatar fratzinger commented on August 22, 2024

It's not. It's only called on feathersClient.authenticate(...). My use case is, that defineAbilitiesFor() populates all permissions, that are available for that user.
Why do you want to run defineAbilitiesFor on every request?

from feathers-casl.

dasantonym avatar dasantonym commented on August 22, 2024

Well, I guess I am just using it wrong. The thing that bothers me is that if I make a find request for a service:

  • The authenticate('auth0') hook in before.all validates the token and adds a user to params.
  • Next in line is the authorize({ adapter: 'feathers-mongoose' }) hook in before.find, but that one skips because abilities is not set.

Sorry if I'm being stupid here, but I don't see how the abilities get enforced on the request result here.

from feathers-casl.

fratzinger avatar fratzinger commented on August 22, 2024

What's your transport? express.js or socket.io?

from feathers-casl.

dasantonym avatar dasantonym commented on August 22, 2024

That's express. Thanks for the quick response, btw.

from feathers-casl.

fratzinger avatar fratzinger commented on August 22, 2024

Unfortunately it's not working for express the way it's stated in the docs. I noticed that a few days ago. There should be a note in the docs and a recommended way for express.
@dasantonym Are you up to add that to the docs in any way? Any help would be appreciated.

from feathers-casl.

dasantonym avatar dasantonym commented on August 22, 2024

I see, alright then, at least I can stop worrying about my sanity. Thanks for the clarification and sure, I'll add the relevant info to the docs. I'll open a pull request and you can check if I got everything right.

from feathers-casl.

fratzinger avatar fratzinger commented on August 22, 2024

Nice. Thanks in advance and sorry for the confusion!
Greets from Rostock to Mainz :)

from feathers-casl.

dasantonym avatar dasantonym commented on August 22, 2024

Hehe, never mind and hallo nach Rostock!

from feathers-casl.

dasantonym avatar dasantonym commented on August 22, 2024

Ah, actually one more question: In my hook I am only adding the ability prop to params and not the rules. It works, but are the rules needed there?

from feathers-casl.

fratzinger avatar fratzinger commented on August 22, 2024

The rules aren't used on the server anywhere. In my application (mainly socket.io), the rules get populated to the authResult and therefore are in the response on the client side. I use vue along with @casl/vue to use the permissions on the client side, which is pretty useful for UX (disabling/enabling buttons). I also have rules like: can('view', 'dashboard') stored in my db. The example for vue.js is also in the docs.

from feathers-casl.

dasantonym avatar dasantonym commented on August 22, 2024

Alright, thanks for clarifying, I hadn't looked at the Vue integration yet at all. Very nice!

from feathers-casl.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.