Skip to content

Commit d586b88

Browse files
committed
Fixed reactivity bug in acl.user of useAcl() composition api. Added .user sematic alias. Added option to pass user as fourth argument to the vue-router meta async function.
1 parent d5dc480 commit d586b88

File tree

5 files changed

+100
-36
lines changed

5 files changed

+100
-36
lines changed

README.md

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,35 @@ A simple unopinionated Vue plugin for managing user roles and permissions, acces
2323
</div>
2424

2525
## Table of Contents
26-
* [Features](#features)
27-
* [Installation](#installation)
28-
* [Usage](#usage)
29-
* [Usage with Vue 3](#usage-vue3)
30-
* [Usage with Vue 2](#usage-vue2)
31-
* [ACL Rules File](#acl-file)
32-
* [Usage in component](#usage-in-component)
33-
* [Using helper function in component](#using-helper)
34-
* [Using helper function in `setup` Vue's Composition API](#composition-api)
35-
* [Middleware for Vue Router](#middleware-for-vue-router)
36-
* [`onDeniedRoute` meta property](#vue-router-ondeniedroute)
37-
* [Vue Router `meta` Properties](#vue-router-meta)
38-
* [Semantic Alias methods and directives](#semantic-alias)
39-
* [Vue Simple ACL Options](#options)
40-
* [TODO](#todo)
41-
* [Contributing](#contributing)
42-
* [Support](#support)
43-
* [License](#license)
26+
- [Vue Simple ACL](#vue-simple-acl)
27+
- [Table of Contents](#table-of-contents)
28+
- [Features](#features)
29+
- [Installation](#installation)
30+
- [NPM](#npm)
31+
- [Yarn](#yarn)
32+
- [CDN](#cdn)
33+
- [Usage](#usage)
34+
- [Usage with Vue 3](#usage-with-vue-3)
35+
- [Usage with Vue 2](#usage-with-vue-2)
36+
- [ACL Rules File](#acl-rules-file)
37+
- [Usage in component](#usage-in-component)
38+
- [`hide` modifier](#hide-modifier)
39+
- [`disable` modifier](#disable-modifier)
40+
- [`readonly` modifier](#readonly-modifier)
41+
- [`not` modifier](#not-modifier)
42+
- [`any` modifier](#any-modifier)
43+
- [Using helper function in component](#using-helper-function-in-component)
44+
- [Using helper function in `setup` Vue's Composition API](#using-helper-function-in-setup-vues-composition-api)
45+
- [Middleware for Vue Router](#middleware-for-vue-router)
46+
- [`onDeniedRoute` meta property](#ondeniedroute-meta-property)
47+
- [$from as value `onDeniedRoute`](#from-as-value-ondeniedroute)
48+
- [Vue Router `meta` Properties](#vue-router-meta-properties)
49+
- [Semantic Alias directives and methods](#semantic-alias-directives-and-methods)
50+
- [Vue Simple ACL Options](#vue-simple-acl-options)
51+
- [TODO](#todo)
52+
- [🤝 Contributing](#-contributing)
53+
- [⭐️ Support](#️-support)
54+
- [📄 License](#-license)
4455

4556

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

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

91102
import { createApp } from 'vue'
92103
import App from './App.vue'
104+
import router from './store';
93105
import store from './store';
94106
import acl from './acl'; // import the instance of the defined ACL
95107

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

109122
import Vue from 'vue'
110123
import App from './App.vue'
124+
import router from './router';
111125
import store from './store';
112126
import acl from './acl'; // import the instance of the defined ACL
113127

114128
Vue.config.productionTip = false;
115129

116-
Vue.use(store);
117130
Vue.use(acl); // install vue-simple-acl
131+
118132
new Vue({
133+
router,
134+
store,
119135
render: h => h(App),
120136
}).$mount('#app')
121137
```
@@ -306,7 +322,7 @@ export default {
306322
// Execute this block if user is admin OR can delete post
307323
}
308324

309-
// Get data of the ACL User being validated
325+
// Get data of the defined ACL User being validated
310326
const user = acl.user;
311327
}
312328
}
@@ -404,6 +420,20 @@ or using `any` modifier
404420
}
405421
}
406422
```
423+
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
424+
```javascript
425+
{
426+
path: 'posts/:postId/publish',
427+
component: ManagePost,
428+
meta: {
429+
anyCan: (to, from, anyCan, user) => {
430+
return axios.get(`/api/users/${user.id}/posts/${to.params.id}/publish`)
431+
.then((response) => anyCan(['is-admin', ['edit-post', response.data]]));
432+
},
433+
onDeniedRoute: '/unauthorized'
434+
}
435+
}
436+
```
407437

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

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

429459
| Property Name | Type | Default | Description |
430460
| --- | --- | --- | --- |
431-
| **can** or **allCan**| `string` OR `array` of abilities or `function (to, from, can)` of async evaluation | None | Equivalent of `$can()` and `v-can=""` |
432-
| **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=""` |
433-
| **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=""` |
461+
| **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=""` |
462+
| **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=""` |
463+
| **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=""` |
434464
| **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 |
435465

436466

@@ -444,6 +474,7 @@ Vue Simple ACL also provides some directives and methods in different verb as al
444474
| 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>`}` |
445475
| 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']` |
446476
| 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>`}` |
477+
| 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>`}` |
447478

448479

449480
<a name="options"></a>

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vue-simple-acl",
3-
"version": "1.0.5",
3+
"version": "1.1.0",
44
"description": "A simple unopinionated Vue plugin for managing user roles and permissions, access-control list (ACL) and role-based access control (RBAC).",
55
"main": "dist/vue-simple-acl.js",
66
"browser": "dist/vue-simple-acl.min.js",

playground/src/App.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
<br>
2727
<input v-can:edit-post.disabled="post2" v-model="postTitle" type="text">
2828
<br>...
29-
{{ $acl }}
29+
{{ $acl.user }}
3030
<div v-if="$acl.role('edit-post', post)">
3131
#1. HELPER: USER-1 can create POST
3232
</div>
@@ -71,8 +71,9 @@ export default defineComponent({
7171
} else {
7272
console.log('User CAN NOT edit post');
7373
}
74+
console.log(acl.user);
75+
7476
75-
// console.log(acl)
7677
7778
return {
7879
post,

playground/src/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const routes = [
2323
path:'/post/:id',
2424
component: About,
2525
meta: {
26-
can: (to, from, can) => {
26+
can: (to, from, can, user) => {
2727
return can('edit-post', { id: 100, user_id: 2 });
2828
// return axios.get(`/api/posts/${to.params.id}`)
2929
// .then((response) => can('edit-post', response.data));

src/VueSimpleAcl.ts

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { reactive } from 'vue';
1+
import { reactive, computed } from 'vue';
22
import { getFunctionArgsNames, capitalize } from './utils';
33
import { State, PluginOption, Ability, AbilityArgs, AbilitiesEvaluationProps } from './@types';
44

@@ -432,6 +432,18 @@ export const installPlugin = (app: any, options?: PluginOption) => {
432432
registerHelper(app, 'roles', isVue3, true);
433433
registerHelper(app, 'roleOrPermission', isVue3, true);
434434
registerHelper(app, 'roleOrPermissions', isVue3, true);
435+
// Add user data to the global variable as property
436+
if (isVue3) {
437+
if (!app.config.globalProperties.$acl) {
438+
app.config.globalProperties.$acl = {};
439+
}
440+
app.config.globalProperties.$acl.user = computed(() => state.registeredUser).value;
441+
} else {
442+
if (!app.prototype.$acl) {
443+
app.prototype.$acl = {};
444+
}
445+
app.prototype.$acl.user = computed(() => state.registeredUser).value;
446+
}
435447
}
436448

437449

@@ -463,8 +475,13 @@ export const installPlugin = (app: any, options?: PluginOption) => {
463475
if (to.meta && (to.meta.can || to.meta.permission || to.meta.role || to.meta.roleOrPermission)) {
464476
const abilities = (to.meta.can || to.meta.permission || to.meta.role || to.meta.roleOrPermission);
465477
let granted = false;
466-
if (typeof abilities === 'function') {
467-
granted = abilities(to, from, canHelperHandler);
478+
if (typeof abilities === 'function') {
479+
const funcArgs = getFunctionArgsNames(abilities);
480+
if (Array.isArray(funcArgs) && funcArgs.length === 4) {
481+
granted = abilities(to, from, canHelperHandler, state.registeredUser);
482+
} else {
483+
granted = abilities(to, from, canHelperHandler);
484+
}
468485
} else {
469486
granted = canHelperHandler(abilities)
470487
}
@@ -474,7 +491,12 @@ export const installPlugin = (app: any, options?: PluginOption) => {
474491
const abilities = (to.meta.canAll || to.meta.allCan || to.meta.allPermission || to.meta.allRole || to.meta.allRoleOrPermission);
475492
let granted = false;
476493
if (typeof abilities === 'function') {
477-
granted = abilities(to, from, canHelperHandler);
494+
const funcArgs = getFunctionArgsNames(abilities);
495+
if (Array.isArray(funcArgs) && funcArgs.length === 4) {
496+
granted = abilities(to, from, canHelperHandler, state.registeredUser);
497+
} else {
498+
granted = abilities(to, from, canHelperHandler);
499+
}
478500
} else {
479501
granted = canHelperHandler(abilities)
480502
}
@@ -484,7 +506,12 @@ export const installPlugin = (app: any, options?: PluginOption) => {
484506
const abilities = (to.meta.cannot || to.meta.canNot || to.meta.notCan || to.meta.notPermission || to.meta.notRole || to.meta.notRoleOrPermission);
485507
let granted = false;
486508
if (typeof abilities === 'function') {
487-
granted = abilities(to, from, notCanHelperHandler);
509+
const funcArgs = getFunctionArgsNames(abilities);
510+
if (Array.isArray(funcArgs) && funcArgs.length === 4) {
511+
granted = abilities(to, from, notCanHelperHandler, state.registeredUser);
512+
} else {
513+
granted = abilities(to, from, notCanHelperHandler);
514+
}
488515
} else {
489516
granted = notCanHelperHandler(abilities)
490517
}
@@ -494,7 +521,12 @@ export const installPlugin = (app: any, options?: PluginOption) => {
494521
const abilities = (to.meta.canAny || to.meta.anyCan || to.meta.anyPermission || to.meta.anyRole|| to.meta.anyRoleOrPermission);
495522
let granted = false;
496523
if (typeof abilities === 'function') {
497-
granted = abilities(to, from, anyCanHelperHandler)
524+
const funcArgs = getFunctionArgsNames(abilities);
525+
if (Array.isArray(funcArgs) && funcArgs.length === 4) {
526+
granted = abilities(to, from, anyCanHelperHandler, state.registeredUser);
527+
} else {
528+
granted = abilities(to, from, anyCanHelperHandler);
529+
}
498530
} else {
499531
granted = anyCanHelperHandler(abilities);
500532
}
@@ -558,7 +590,7 @@ export const defineAclRules = (aclRulesCallback: Function): void => {
558590
*/
559591
export const useAcl = () => {
560592
let acl: any = {};
561-
acl.user = state.registeredUser;
593+
acl.user = computed(() => state.registeredUser).value;
562594
//
563595
acl.can = canHelperHandler;
564596
acl.can.not = notCanHelperHandler;

0 commit comments

Comments
 (0)