Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

change AbilityBuilder on state update #148

Closed
ahmadiehsan opened this issue Jan 11, 2019 · 13 comments
Closed

change AbilityBuilder on state update #148

ahmadiehsan opened this issue Jan 11, 2019 · 13 comments
Labels

Comments

@ahmadiehsan
Copy link

ahmadiehsan commented Jan 11, 2019

I have below code in ability.js

// Defines how to detect object's type
function subjectName(item) {
    if (!item || typeof item === 'string') {
        return item;
    }
    return item.__type;
}

let state = null;
store.subscribe(() => {state = store.getState()});

export default AbilityBuilder.define({subjectName}, can => {
    can('delete', 'Post', {userId: state.auth.userId});
});

and i use redux and react-redux for handling my state in react app

my question is, how can i update my AbilityBuilder when my app's state updated?
because my userId come from state.

thanks :)

@stalniy
Copy link
Owner

stalniy commented Jan 11, 2019

Hi,

You don't need to update AbilityBuilder. It's just a helper class.
What you want to do is to update rules in your Ability instance.

So, the approximate code should be something like this:

// Defines how to detect object's type
function subjectName(item) {
    if (!item || typeof item === 'string') {
        return item;
    }
    return item.__type;
}

const ability = new Ability([], { subjectName })

let currentAuth;
store.subscribe(() => {
   const prevAuth = currentAuth
   currentAuth = store.getState().auth

   if (prevAuth !== currentAuth) {
      ability.update(defineRulesFor(currentAuth))
   }
});

function defineRulesFor(auth) {
  const { can, rules } = AbilityBuilder.extract()

  can('delete', 'Post', { userId: auth.userId });

  return rules
}

export default ability

@stalniy
Copy link
Owner

stalniy commented Jan 16, 2019

@ahmadiehsan does this help you?

@stalniy
Copy link
Owner

stalniy commented Jan 18, 2019

Close due to inactivity

@tanh123678
Copy link

i do exactly the same to you but i cant update ability . please tell me why ??? when i logout and login to another user, the ability didn't update.

@stalniy
Copy link
Owner

stalniy commented May 2, 2019

@tanh123678 please check that you use the latest version of casl/ability and casl/react packages

@stalniy
Copy link
Owner

stalniy commented May 2, 2019

Also please check this comment #174 (comment)

The guy had the similar issue and in PR to his example I explained where he used CASL and react incorrectly.

@vincent-lu
Copy link

Hi guys I'm having the same problem that @tanh123678 is having: logout and login as different user won't have ability settings updated. The app is written in vue and using @casl/vue.

Is there a way for me to empty all ability at logout? Why is Vue.use(abilitiesPlugin, defineAbilitiesFor(user)); not overriding old config?

Thanks.

@stalniy
Copy link
Owner

stalniy commented Sep 12, 2019

You need to update ability on login and logout Vue.use(abilitiesPlugin) doesn’t magically detect login/logout and executed only once when app bootstraps (on page reload).

That’s why your abilities are not updated

@vincent-lu
Copy link

Thanks @stalniy I'm running Vue.use(abilitiesPlugin, defineAbilitiesFor(user)); at login but it doesn't seem to be overriding the previously configed settings.

@stalniy
Copy link
Owner

stalniy commented Sep 12, 2019

You shouldn’t do this :)

This line should be written only once. Somewhere in plugins or in main.js.

Please check this answer https://stackoverflow.com/questions/53958961/update-user-in-casl and example with login flow via Vuex (https://github.com/stalniy/casl-vue-api-example)

@vincent-lu
Copy link

vincent-lu commented Sep 12, 2019

Hi @stalniy thanks for the guidance.

Currently my code does the following:
In main.js:

import Vue from 'vue';
import { abilitiesPlugin } from '@casl/vue';
Vue.use(abilitiesPlugin);

Also in main.js but when user data is ready (I'm using @websanova/vue-auth):

  mounted() {
    this.$auth.ready(() => {
      this.$store.commit('ability/initialise', this.$auth.user());
    });
  },

And in ability.js store:

import { Ability, AbilityBuilder } from '@casl/ability';

const defineRulesFor = (user) => {
  const { can, rules } = AbilityBuilder.extract();

  switch (user.role) {
    case 'admin':
      can('create', 'all');
      can('update', 'all');
      can('read', 'all');
      can('delete', 'all');
      break;

    default:
  }

  return rules;
};

const mutations = {
  initialise(state, user) {
    const ability = new Ability([]);
    ability.update(defineRulesFor(user));

    state.initialised = true;
  },
};

I can see defineRulesFor is run with correct rules returned. But when I try to get this.$can('create', 'all') I'm still getting false;

@stalniy
Copy link
Owner

stalniy commented Sep 12, 2019

you create ability inside mutations.intialize, how can the rest of the world (and particularly Vue app instance) get access to it? There is no way because it's a local variable inside a function. so, you have 2 instance 1 created by abilitiesPlugin (it creates empty ability instance) and another one which you create in your mutations (which actually dies right after this function execution ends).

Please pay attention to how ability instance is shared between vuex and and plugin in the provided example!

@vincent-lu
Copy link

vincent-lu commented Sep 12, 2019

Thanks @stalniy; totally forgot about the scope of access for ability.

I've reworked my code to the follow for anyone else stumble upon this issue:

main.js:

import Vue from 'vue';
import { abilitiesPlugin } from '@casl/vue';
import defineRulesFor from './imports/ability';

Vue.use(abilitiesPlugin);

// in main vue component
// this.$auth is from @websanova/vue-auth
// this is for when browser refreshes
mounted() { // run every time main dom is mounted
  this.$auth.ready(() => { // call casl ability.update() when $auth.user() is ready
    this.$ability.update(defineRulesFor(this.$auth.user()));
  });
}

imports/ability.js:

import { AbilityBuilder } from '@casl/ability';

export default (user) => {
  const { can, rules } = AbilityBuilder.extract();

  switch (user.role) {
    case 'admin':
      can('create', 'all');
      can('update', 'all');
      can('read', 'all');
      can('delete', 'all');
      break;

    default:
  }

  return rules;
};

login and logout methods:
needed because when logged in the the mounted() in main dom won't get run again.

import defineRulesFor from './imports/ability';

login() {
  this.$auth.login({
    ...
    success() {
      this.$ability.update(defineRulesFor(this.$auth.user()));
    },
  });
},

logout() {
  this.$auth.logout({
    ...
    success() {
      this.$ability.update([]);
    },
  });
},

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants