JiriChara / vue-kindergarten

Modular security for Vue, Vuex, Vue-Router and Nuxt
MIT License
312 stars 24 forks source link

child reactivity #8

Closed blackpuppy closed 7 years ago

blackpuppy commented 7 years ago

I am still quite new to Vue.js. Maybe I made a mistake somewhere with reactivity of Vuex state.

I am using Vue.js 2 with Vuex, vue-router, etc. I dispatch login action to authenticate a user. My problem is: after logging in, the authroization is not reflected.

To debug the problem, I added the following condole.debug:

// in app.js
Vue.use(VueKindergarten, {
    child: (store) => {
        const child = store.state.auth.user;
        console.debug('VueKindergarten: child = ', child);
        return child;
    },
});

and

// in perimeters/BasePerimeter.js
export default class BasePerimeter extends Perimeter {
    isAdmin() {
        console.debug('BasePerimeter.isAdmin():');
        console.debug('  this.child is ', (this.child && this.child.full_name));
        console.debug('  this.child = ', this.child);
        return this.child && this.child.role &&
            this.child.role.name === 'Administrator';
    }
...

What I see is that BasePerimeter.isAdmin() is called before child is set to the logged in user. The problem is after child is set to the logged in user, BasePerimeter.isAdmin() is not called again and authorization is not refreshed.

Is it possible that the problem is related with any potential issue with vue-kindergarten's child reactivity, or some mistakes in my Vuex store implementation? If needed, I can show the relevant part of my Vuex store modules. It's a bit lengthy.

Thanks! Zhu Ming

blackpuppy commented 7 years ago

I have a workaround to use store instead of the user state inside the store as child:

// in app.js
Vue.use(VueKindergarten, {
    child: (store) => store,
});

and

// in perimeters/BasePerimeter.js
export default class BasePerimeter extends Perimeter {
    isAdmin() {
        return allowed = !!this.child
            && this.child.getters['auth/isLoggedIn']
            && this.child.getters['auth/isAdmin'];
    }
...

Inside my store's auth module, I encapsulates the logic to check admin access in the getter isAdmin. And it works!

I would be glad to hear any improvements or suggestions.

Thanks! Zhu Ming

JiriChara commented 7 years ago

Hey @blackpuppy,

I usually put the getter of a current user to separate file for example src/child.js, because this way I can also use it wherever I want. The best way to assure that reactivity will work is to make child a function:

// src/child.js
import store from '@/store';

export default () => () => store.state.users.current;
import Vue from 'vue';
import VueKindergarten from 'vue-kindergarten';
import child from '@/child';

Vue.use(VueKindergarten, {
    child,
});

And you can use it like this:

import { Perimeter } from 'vue-kindergarten';

export default class BasePerimeter extends Perimeter {
  isLoggedIn() {
    return !!this.child() || false;
  }
  isAdmin() {
    return this.child().role === 'admin';
  }
}

Please let me know if it works for you.

JiriChara commented 7 years ago

@blackpuppy @wdews I have fixed the issue with child reactivity, so it will work even if child references a property in the store. You can upgrade :balloon:

blackpuppy commented 7 years ago

@JiriChara

The fix works as expected.

I changed the child to return my user directly as below:

export default (store) => store ? store.state.auth.user : null;

Now I use Vuex store modules, and user is in the module auth's state.

And I changed my BasePerimeter as below:

import { Perimeter } from 'vue-kindergarten';

export default class BasePerimeter extends Perimeter {
    isAdmin() {
        return this.child && this.child.role &&
            this.child.role.name === 'Administrator';
    }

    isAccountManager() {
        return this.child && this.child.role &&
            this.child.role.name === 'Account Manager';
    }
}

Then the previous problem does not happen. From UI, I see that the menu items shown are always the ones for the user I log in as.

Thanks! Zhu Ming

branquito commented 7 years ago

What if I need to override the constructor in my BasePerimeter class, what are the parameters I have to supply to super() then, for everything to continue working as expected? For example, I use constructor() to generate class methods dynamically for isAdmin, isWhatEver, based on roles array.

branquito commented 7 years ago

I think I got it to work, by looking at Perimeter.js class, then passing in purpose, opts = {}.