Skip to content

Commit

Permalink
Fixed reactivity bug in acl.user of useAcl() composition api. Added .…
Browse files Browse the repository at this point in the history
…user sematic alias. Added option to pass user as fourth argument to the vue-router meta async function.
  • Loading branch information
victorybiz committed Mar 23, 2021
1 parent d5dc480 commit d586b88
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 36 deletions.
81 changes: 56 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,35 @@ A simple unopinionated Vue plugin for managing user roles and permissions, acces
</div>

## Table of Contents
* [Features](#features)
* [Installation](#installation)
* [Usage](#usage)
* [Usage with Vue 3](#usage-vue3)
* [Usage with Vue 2](#usage-vue2)
* [ACL Rules File](#acl-file)
* [Usage in component](#usage-in-component)
* [Using helper function in component](#using-helper)
* [Using helper function in `setup` Vue's Composition API](#composition-api)
* [Middleware for Vue Router](#middleware-for-vue-router)
* [`onDeniedRoute` meta property](#vue-router-ondeniedroute)
* [Vue Router `meta` Properties](#vue-router-meta)
* [Semantic Alias methods and directives](#semantic-alias)
* [Vue Simple ACL Options](#options)
* [TODO](#todo)
* [Contributing](#contributing)
* [Support](#support)
* [License](#license)
- [Vue Simple ACL](#vue-simple-acl)
- [Table of Contents](#table-of-contents)
- [Features](#features)
- [Installation](#installation)
- [NPM](#npm)
- [Yarn](#yarn)
- [CDN](#cdn)
- [Usage](#usage)
- [Usage with Vue 3](#usage-with-vue-3)
- [Usage with Vue 2](#usage-with-vue-2)
- [ACL Rules File](#acl-rules-file)
- [Usage in component](#usage-in-component)
- [`hide` modifier](#hide-modifier)
- [`disable` modifier](#disable-modifier)
- [`readonly` modifier](#readonly-modifier)
- [`not` modifier](#not-modifier)
- [`any` modifier](#any-modifier)
- [Using helper function in component](#using-helper-function-in-component)
- [Using helper function in `setup` Vue's Composition API](#using-helper-function-in-setup-vues-composition-api)
- [Middleware for Vue Router](#middleware-for-vue-router)
- [`onDeniedRoute` meta property](#ondeniedroute-meta-property)
- [$from as value `onDeniedRoute`](#from-as-value-ondeniedroute)
- [Vue Router `meta` Properties](#vue-router-meta-properties)
- [Semantic Alias directives and methods](#semantic-alias-directives-and-methods)
- [Vue Simple ACL Options](#vue-simple-acl-options)
- [TODO](#todo)
- [🤝 Contributing](#-contributing)
- [⭐️ Support](#️-support)
- [📄 License](#-license)


<br>
Expand All @@ -51,15 +62,15 @@ A simple unopinionated Vue plugin for managing user roles and permissions, acces
- Vue 2 and Vue 3 support
- Simple but robust and power ACL plugin
- Manage roles and permissions with ease.
- Lightweight (<10 kB zipped)
- Lightweight (<3 kB zipped)
- Component `v-can` directive
- Global `$can` helper function
- Sematic alias methods and directives of different verb for directive and helper function. E.g `v-role`, `v-permission`, `$acl.permission()`, `$acl.anyRole()`, etc.
- Middleware support for [Vue Router](https://next.router.vuejs.org/) through `meta` property.
- Support user data from plain object, vuex store and asynchronous function.
- Reactive changes of abilities and permissions
- Define custom ACL rules
- TypeScript Support
- Fully Typecript: The source code is written entirely in TypeScript.
- Fully configurable

<a name="installation"></a>
Expand Down Expand Up @@ -90,10 +101,12 @@ yarn add vue-simple-acl

import { createApp } from 'vue'
import App from './App.vue'
import router from './store';
import store from './store';
import acl from './acl'; // import the instance of the defined ACL

const app = createApp(App);
app.use(router);
app.use(store);
app.use(acl); // install vue-simple-acl
app.mount("#app");
Expand All @@ -108,14 +121,17 @@ In Vue 2, when using User data from reactive Store/Vuex wrapped with `computed()

import Vue from 'vue'
import App from './App.vue'
import router from './router';
import store from './store';
import acl from './acl'; // import the instance of the defined ACL

Vue.config.productionTip = false;

Vue.use(store);
Vue.use(acl); // install vue-simple-acl

new Vue({
router,
store,
render: h => h(App),
}).$mount('#app')
```
Expand Down Expand Up @@ -306,7 +322,7 @@ export default {
// Execute this block if user is admin OR can delete post
}

// Get data of the ACL User being validated
// Get data of the defined ACL User being validated
const user = acl.user;
}
}
Expand Down Expand Up @@ -404,6 +420,20 @@ or using `any` modifier
}
}
```
or get the data of the defined ACL user in the evaluations by passing `user` as the optional **fourth** argument to the defined ACL meta function
```javascript
{
path: 'posts/:postId/publish',
component: ManagePost,
meta: {
anyCan: (to, from, anyCan, user) => {
return axios.get(`/api/users/${user.id}/posts/${to.params.id}/publish`)
.then((response) => anyCan(['is-admin', ['edit-post', response.data]]));
},
onDeniedRoute: '/unauthorized'
}
}
```

<a name="vue-router-ondeniedroute"></a>

Expand All @@ -428,9 +458,9 @@ You can set the onDeniedRoute to the special value `'$from'` which will return t

| Property Name | Type | Default | Description |
| --- | --- | --- | --- |
| **can** or **allCan**| `string` OR `array` of abilities or `function (to, from, can)` of async evaluation | None | Equivalent of `$can()` and `v-can=""` |
| **notCan** or **canNot** | `string` OR `array` of abilities or `function (to, from, notCan)` of async evaluation | None | Equivalent of `$can.not()` and `v-can.not=""` |
| **anyCan** or **canAny**| `string` OR `array` of abilities or `function (to, from, anyCan)` of async evaluation | None | Equivalent of `$can.any()` and `v-can.any=""` |
| **can** or **allCan**| `string` OR `array` of abilities OR `function` of asynchronous evaluation: <span style="white-space:nowrap;">`(to, from, can, user?) => {}`</span> | None | Equivalent of `$can()` and `v-can=""` |
| **notCan** or **canNot** | `string` OR `array` of abilities OR `function` of asynchronous evaluation: <span style="white-space:nowrap;">`(to, from, notCan, user?)`</span> | None | Equivalent of `$can.not()` and `v-can.not=""` |
| **anyCan** or **canAny**| `string` OR `array` of abilities OR `function` of asynchronous evaluation: <span style="white-space:nowrap;">`(to, from, anyCan, user?)`</span> | None | Equivalent of `$can.any()` and `v-can.any=""` |
| **onDeniedRoute** | `string` OR `object` of `route()` option | Value of the default option `onDeniedRoute` | A route to redirect to when `can|notCan|anyCan` evaluation is denied. e.g string path `'/unauthorized'` OR router option `{ path: '/unauthorized' }` OR `{ name: 'unauthorizedPage', replace: true }` OR special value **`'$from'`** which returns back to the request URI |


Expand All @@ -444,6 +474,7 @@ Vue Simple ACL also provides some directives and methods in different verb as al
| Permission | As Directives:<br>`v-permission:create-post`<br>`v-permission="'create-post'"`<br> `v-permission.not="'create-post'"`<br> `v-permission.any="['create-post', ['edit-post', post]]"` <br><br> In Component:<br> `$acl.permission('create-post')`<br> `$acl.notPermission('create-post')`<br> `$acl.anyPermission(['create-post', ['edit-post', post]])` <br><br> In Option API:<br> `this.$acl.permission('create-post')`<br> `this.$acl.notPermission('create-post')`<br> `this.$acl.anyPermission(['create-post', ['edit-post', post]])` <br><br> In Composition API/`setup()`:<br> `const acl = useAcl();`<br> `acl.permission('create-post')`<br> `acl.notPermission('create-post')`<br> `acl.anyPermission(['create-post', ['edit-post', post]])` <br><br> In Vue Router `meta` Property:<br> `permission: 'create-post'`<br> `notPermission: ['create-post', 'create-category']` <br><br> `anyPermission: (to, from, anyPermission) => {`<br>&nbsp;&nbsp;`return axios.get(`\``/api/posts/${to.params.id}`\``)`<br>&nbsp;&nbsp;`.then((response) => anyPermission(['create-post', ['edit-post', response.data]]));`<br>`}` |
| Role | As Directives:<br>`v-role:admin`<br>`v-role="'admin'"`<br> `v-role.not="'editor'"`<br> `v-role.any="['admin', 'editor']"` <br><br> In Component:<br> `$acl.role('admin')`<br> `$acl.notRole('editor')`<br> `$acl.anyRole(['admin', 'editor'])` <br><br> In Option API:<br> `this.$acl.role('admin')`<br> `this.$acl.notRole('editor')`<br> `this.$acl.anyRole(['admin', 'editor'])` <br><br> In Composition API/`setup()`:<br> `const acl = useAcl();`<br> `acl.role('admin')`<br> `acl.notRole('editor')`<br> `acl.anyRole(['admin', 'editor'])` <br><br> In Vue Router `meta` Property:<br> `role: 'admin'`<br> `notRole: 'editor'` <br> `anyRole: ['admin', 'editor']` |
| Role Or Permission | As Directives:<br>`v-role-or-permission="['admin', 'create-post']"`<br> `v-role-or-permission.not="['editor', 'create-post']"`<br> `v-role-or-permission.any="['admin', 'create-post', ['edit-post', post]]"` <br><br> In Component:<br> `$acl.roleOrPermission(['admin', 'create-post'])`<br> `$acl.notRoleOrPermission(['editor', 'create-post'])`<br> `$acl.anyRoleOrPermission(['admin', 'create-post', ['edit-post', post]])` <br><br> In Option API:<br> `this.$acl.roleOrPermission(['admin', 'create-post'])`<br> `this.$acl.notRoleOrPermission(['editor', 'create-post'])`<br> `this.$acl.anyRoleOrPermission(['admin', 'create-post', ['edit-post', post]])` <br><br> In Composition API/`setup()`:<br> `const acl = useAcl();`<br> `acl.roleOrPermission(['admin', 'create-post'])`<br> `acl.notRoleOrPermission(['editor', 'create-post'])`<br> `acl.anyRoleOrPermission(['admin', 'create-post', ['edit-post', post]])` <br><br> In Vue Router `meta` Property:<br> `roleOrPermission: ['admin', 'create-post']`<br> `notRoleOrPermission: ['editor', 'create-post', 'create-category']` <br><br> `anyRoleOrPermission: (to, from, anyRoleOrPermission) => {`<br>&nbsp;&nbsp;`return axios.get(`\``/api/posts/${to.params.id}`\``)`<br>&nbsp;&nbsp;`.then((response) => anyRoleOrPermission(['admin', 'create-post', ['edit-post', response.data]]));`<br>`}` |
| User | Get the data of the defined ACL user. <br><br> In Component:<br> `$acl.user; // returns user object` <br><br> In Option API:<br> `this.$acl.user; // returns user object` <br><br> In Composition API/`setup()`:<br> `const acl = useAcl();`<br> `acl.user; // returns user object` <br><br> In Vue Router `meta` Property:<br> _Pass `user` as the fourth argument to the defined ACL meta function_ <br><br> `roleOrPermission: (to, from, roleOrPermission, user) => {`<br>&nbsp;&nbsp;`return axios.get(`\``/api/users/${user.id}/posts/${to.params.id}`\``)`<br>&nbsp;&nbsp;`.then((response) => roleOrPermission(['admin', ['edit-post', response.data]]));`<br>`}` |


<a name="options"></a>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vue-simple-acl",
"version": "1.0.5",
"version": "1.1.0",
"description": "A simple unopinionated Vue plugin for managing user roles and permissions, access-control list (ACL) and role-based access control (RBAC).",
"main": "dist/vue-simple-acl.js",
"browser": "dist/vue-simple-acl.min.js",
Expand Down
5 changes: 3 additions & 2 deletions playground/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
<br>
<input v-can:edit-post.disabled="post2" v-model="postTitle" type="text">
<br>...
{{ $acl }}
{{ $acl.user }}
<div v-if="$acl.role('edit-post', post)">
#1. HELPER: USER-1 can create POST
</div>
Expand Down Expand Up @@ -71,8 +71,9 @@ export default defineComponent({
} else {
console.log('User CAN NOT edit post');
}
console.log(acl.user);
// console.log(acl)
return {
post,
Expand Down
2 changes: 1 addition & 1 deletion playground/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const routes = [
path:'/post/:id',
component: About,
meta: {
can: (to, from, can) => {
can: (to, from, can, user) => {
return can('edit-post', { id: 100, user_id: 2 });
// return axios.get(`/api/posts/${to.params.id}`)
// .then((response) => can('edit-post', response.data));
Expand Down
46 changes: 39 additions & 7 deletions src/VueSimpleAcl.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { reactive } from 'vue';
import { reactive, computed } from 'vue';
import { getFunctionArgsNames, capitalize } from './utils';
import { State, PluginOption, Ability, AbilityArgs, AbilitiesEvaluationProps } from './@types';

Expand Down Expand Up @@ -432,6 +432,18 @@ export const installPlugin = (app: any, options?: PluginOption) => {
registerHelper(app, 'roles', isVue3, true);
registerHelper(app, 'roleOrPermission', isVue3, true);
registerHelper(app, 'roleOrPermissions', isVue3, true);
// Add user data to the global variable as property
if (isVue3) {
if (!app.config.globalProperties.$acl) {
app.config.globalProperties.$acl = {};
}
app.config.globalProperties.$acl.user = computed(() => state.registeredUser).value;
} else {
if (!app.prototype.$acl) {
app.prototype.$acl = {};
}
app.prototype.$acl.user = computed(() => state.registeredUser).value;
}
}


Expand Down Expand Up @@ -463,8 +475,13 @@ export const installPlugin = (app: any, options?: PluginOption) => {
if (to.meta && (to.meta.can || to.meta.permission || to.meta.role || to.meta.roleOrPermission)) {
const abilities = (to.meta.can || to.meta.permission || to.meta.role || to.meta.roleOrPermission);
let granted = false;
if (typeof abilities === 'function') {
granted = abilities(to, from, canHelperHandler);
if (typeof abilities === 'function') {
const funcArgs = getFunctionArgsNames(abilities);
if (Array.isArray(funcArgs) && funcArgs.length === 4) {
granted = abilities(to, from, canHelperHandler, state.registeredUser);
} else {
granted = abilities(to, from, canHelperHandler);
}
} else {
granted = canHelperHandler(abilities)
}
Expand All @@ -474,7 +491,12 @@ export const installPlugin = (app: any, options?: PluginOption) => {
const abilities = (to.meta.canAll || to.meta.allCan || to.meta.allPermission || to.meta.allRole || to.meta.allRoleOrPermission);
let granted = false;
if (typeof abilities === 'function') {
granted = abilities(to, from, canHelperHandler);
const funcArgs = getFunctionArgsNames(abilities);
if (Array.isArray(funcArgs) && funcArgs.length === 4) {
granted = abilities(to, from, canHelperHandler, state.registeredUser);
} else {
granted = abilities(to, from, canHelperHandler);
}
} else {
granted = canHelperHandler(abilities)
}
Expand All @@ -484,7 +506,12 @@ export const installPlugin = (app: any, options?: PluginOption) => {
const abilities = (to.meta.cannot || to.meta.canNot || to.meta.notCan || to.meta.notPermission || to.meta.notRole || to.meta.notRoleOrPermission);
let granted = false;
if (typeof abilities === 'function') {
granted = abilities(to, from, notCanHelperHandler);
const funcArgs = getFunctionArgsNames(abilities);
if (Array.isArray(funcArgs) && funcArgs.length === 4) {
granted = abilities(to, from, notCanHelperHandler, state.registeredUser);
} else {
granted = abilities(to, from, notCanHelperHandler);
}
} else {
granted = notCanHelperHandler(abilities)
}
Expand All @@ -494,7 +521,12 @@ export const installPlugin = (app: any, options?: PluginOption) => {
const abilities = (to.meta.canAny || to.meta.anyCan || to.meta.anyPermission || to.meta.anyRole|| to.meta.anyRoleOrPermission);
let granted = false;
if (typeof abilities === 'function') {
granted = abilities(to, from, anyCanHelperHandler)
const funcArgs = getFunctionArgsNames(abilities);
if (Array.isArray(funcArgs) && funcArgs.length === 4) {
granted = abilities(to, from, anyCanHelperHandler, state.registeredUser);
} else {
granted = abilities(to, from, anyCanHelperHandler);
}
} else {
granted = anyCanHelperHandler(abilities);
}
Expand Down Expand Up @@ -558,7 +590,7 @@ export const defineAclRules = (aclRulesCallback: Function): void => {
*/
export const useAcl = () => {
let acl: any = {};
acl.user = state.registeredUser;
acl.user = computed(() => state.registeredUser).value;
//
acl.can = canHelperHandler;
acl.can.not = notCanHelperHandler;
Expand Down

0 comments on commit d586b88

Please sign in to comment.