- Notifications
You must be signed in to change notification settings - Fork25
A simple unopinionated Vue plugin for managing user roles and permissions, access-control list (ACL) and role-based access control (RBAC).
victorybiz/vue-simple-acl
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
A simple unopinionated Vue plugin for managing user roles and permissions, access-control list (ACL) and role-based access control (RBAC).
- Vue Simple ACL
- Table of Contents
- Features
- Installation
- Usage
- Semantic Alias directives and methods
- Vue Simple ACL Options
- TODO
- 🤝 Contributing
- ⭐️ Support
- 📄 License
- Vue 2 and Vue 3 support
- Simple but robust and power ACL plugin
- Manage roles and permissions with ease.
- Lightweight (<3 kB zipped)
- Component
v-candirective - Global
$canhelper 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 forVue Router through
metaproperty. - Support user data from plain object, pinia/vuex store and asynchronous function.
- Reactive changes of abilities and permissions
- Define custom ACL rules
- Fully Typecript: The source code is written entirely in TypeScript.
- Fully configurable
npm install vue-simple-aclyarn add vue-simple-acl// src/main.js OR src/main.tsimport{createApp}from'vue'importAppfrom'./App.vue'importrouterfrom'./store';importstorefrom'./store';importaclfrom'./acl';// import the instance of the defined ACLconstapp=createApp(App);app.use(router);app.use(store);app.use(acl);// install vue-simple-aclapp.mount("#app");
In Vue 2, when using User data from reactive Store/Pinia/Vuex wrapped withcomputed() function, which is available in Vue 3 as module by default but not in Vue 2, make sure to install@vue/composition-api first and change the imported module to:import { computed } from '@vue/composition-api'
// src/main.js OR src/main.tsimportVuefrom'vue'importAppfrom'./App.vue'importrouterfrom'./router';importstorefrom'./store';importaclfrom'./acl';// import the instance of the defined ACLVue.config.productionTip=false;Vue.use(acl);// install vue-simple-aclnewVue({ router, store,render:h=>h(App),}).$mount('#app')
For readability, it is recommend to defined your ACL rules in a separate file.
// src/acl/index.js OR src/acl/index.ts// Import router if you are using the middleware on Vue Routerimportrouterfrom"../router";// Import store if you are using reactive Store/Pinia/Vuex as User data sourceimportstorefrom"../store";// ----- VUE 3 Imports -----import{computed}from'vue';// For VUE 3import{createAcl,defineAclRules}from'vue-simple-acl';// ----- VUE 2 Imports -----importVuefrom'vue';importVueCompositionAPIfrom'@vue/composition-api';// Ensure this is installedVue.use(VueCompositionAPI);// VueCompositionAPI must be used as plugin before any function, otherwise see your console if warning/errprimport{computed}from'@vue/composition-api';// import computed functionimport{createAcl,defineAclRules}from'vue-simple-acl';// ---------------// The Vue Simple ACL option 'user' can be a user OBJECT, FUNCTION returning a user object// or an Asynchronous function returning a PROMISE of user object, suitable for performing fetch from API.// USER EXAMPLE 1: User {OBJECT}constuser={id:1,name:'Victory Osayi',is_editor:true,is_admin:false,// you can have role based permission list or access control list possibly from databasepermissions:['admin','owner','moderator','create-post','edit-post','delete-post']}// USER EXAMPLE 2: User object from a {FUNCTION} or computed property like from Pinia/Vuex Store// Suitable if you already has an existing logics authenticating and saving user data to Pinia/Vuex Storeconstuser2=computed(()=>store.state.auth.user);// USER EXAMPLE 3; User object from an Asynchronous {FUNCTION} / {PROMISE}:// Using Async/Promise requires instance of vue-router, the function will be auto hooked to beforeEach() peroperty of vue-routerconstuser3=()=>{constauthUserId=1;// ID of authenticated userreturnaxios.get(`api/users/${authUserId}`).then((response)=>response.data);}construles=()=>defineAclRules((setRule)=>{// setRule('unique-ability', callbackFunction(user, arg1, arg2, ...) { });// setRule(['unique-ability-1', 'unique-ability-2'], callbackFunction(user, arg1, arg2, ...) { });// Define a simple rule for ability with no argumentsetRule('create-post',(user)=>user.is_admin||user.is_editor);setRule('is-admin',(user)=>user.is_admin);setRule('is-editor',(user)=>user.is_editor);// Define a simple rule for ability with an argumentsetRule('edit-post',(user,post)=>user.id===post.user_id);setRule('delete-post',(user,post)=>{returnuser.id===post.user_id||user.is_admin;});// Define rule for array of multiple abilities that share same arguments and callback functionsetRule(['publish-post','unpublish-post'],(user,post)=>user.id===post.user_id);// Define rule for ability with multiple argumentssetRule('hide-comment',(user,post,comment)=>{returnuser.is_admin||user.id===post.user_id||(user.id===comment.user_id&&post.id===comment.post_id);});setRule('moderator',(user)=>user.permissions&&user.permissions.includes('moderator'));});constsimpleAcl=createAcl({ user,// short for user: user rules,// short for rules: rules router,// OPTIONAL, short for router: router// other optional vue-simple-acl options here... See Vue Simple ACL Options below});exportdefaultsimpleAcl;
Thev-can directive can be used in different ways and you can apply one or more modifiers that alters the behaviour of the directive.
<buttonv-can:create-post>Create Post</button><buttonv-can:edit-post="{ id: 100, user_id: 1, title: 'First Post' }">Edit</button><buttonv-can:edit-post="postData">Edit</button>
Alternative you can use the sematic alias;
<buttonv-permission:create-post>Create Post</button><buttonv-role:admin>Create Post</button><buttonv-role-or-permission="['admin', 'create-post']">Edit</button>
This is the default behaviour of thev-can directive, it remove the component or element from the DOM more likev-if.You're not required to apply it unless you want to explicitly state the behavior.
<buttonv-can:edit-post.hide="postData">Edit</button>
Thedisable modifier applies the disabled attribute to the tag, e.g. to disable a button or input that you are not allowed to use or edit respectively.
<buttonv-can:edit-post.disable="postData">Edit</button><inputv-can:edit-post.disable="post"v-model="postTitle"type="text">
Thereadonly modifier applies the readonly attribute to the tag, e.g. to make an input read only if you don't have permission to edit.
<inputv-can:edit-post.readonly="post"v-model="postTitle"type="text">
Thenot modifier reverses the ACL query. In this example only if you cannot delete the post the div element is shown.
<divv-can:delete-post.not="postData">You can not delete post created by you, ask admin for help.</div>
By defaultv-can directive with value that contains array of multiple abilities and ability arguments will be authorized if all specified abilities passes.Theany modifier authorized if atleast one or any of specified abilities and ability arguments passes.
<!-- Authorized if both abilities passes --><buttonv-can="['create-post', ['edit-post', post]]">Create Post</button><!-- Authorized if any of the abilities passes --><buttonv-can.any="[['edit-post', post], ['delete-post', post]]">Manage Post</button>
You can also use the helper function $can directly in component and #"auto" data-snippet-clipboard-copy-content="<form v-if="$can('edit-post', post)"> <input type="text" :value="post.title"> ...</form>">
<formv-if="$can('edit-post', post)"><inputtype="text":value="post.title"> ...</form>
or in Option API
if(this.$can('edit-post',post)){axios.put(`/api/posts/${post.id}`,postData)}
Using helper function insetup Vue'sComposition API
The introduction ofsetup and Vue'sComposition API, open up new possibilities but to be able to get the full potential out of Vue Simple ACL, we will need to use composable functions to replace access to this.
import{useAcl}from'vue-simple-acl';exportdefault{setup(){constacl=useAcl();if(acl.can('edit-post',post)){axios.put(`/api/posts/${post.id}`,postData)}if(acl.can('hide-comment',[post,comment])){// Execute this block if user can hide comment of a post}if(acl.can.not('edit-post',post)){// Execute this block if user can not edit post}if(acl.can.any(['is-admin','is-editor'])){// Execute this block if user is admin OR is editor}if(acl.can.any(['is-admin',['delete-post',post]])){// Execute this block if user is admin OR can delete post}// Get data of the defined ACL User being validatedconstuser=acl.user;constuser=acl.getUser();}}
Middleware forVue Router
To integrate Vue Router, hook up the instance ofvue-router'screateRouter({..}) during setup of the Vue Simple ACL.
constsimpleAcl=createAcl({ user,// short for user: user rules,// short for rules: rules router,// short for router: routeronDeniedRoute:'/unauthorized'// OR { path: '/unauthorized' } OR { name: 'unauthorized', replace: true} or '$from'});app.use(simpleAcl);
You configure routes by addingcan meta property to the route. E.g. if a router requires create post permission:
{path:'posts/create',name:'createPost',component:CreatePost,meta:{can:'create-post',onDeniedRoute:'/unauthorized'// value of onDeniedRoute option will be used if not set}}
If you have a rule that requires multiple abilities, you can do the following:
{path:'posts/create',name:'createPost',component:CreatePost,meta:{can:['is-admin','create-post'],onDeniedRoute:{name:'unauthorizedPage',replace:true}}}
or usingnot modifier
{path:'posts/create',name:'createPost',component:CreatePost,meta:{notCan:'moderator',onDeniedRoute:{name:'unauthorizedPage',replace:true}}}
or usingany modifier
{path:'posts/create',name:'createPost',component:CreatePost,meta:{anyCan:['is-admin','create-post'],onDeniedRoute:{name:'unauthorizedPage',replace:true}}}
You can also have an async evaluation by providing a callback that returns a promise the following:
{path:'posts/:postId',component:PostEditor,meta:{can:(to,from,can)=>{returnaxios.get(`/api/posts/${to.params.id}`).then((response)=>can('edit-post',response.data));},onDeniedRoute:'/unauthorized'}}
or usingany modifier
{path:'posts/:postId/publish',component:ManagePost,meta:{anyCan:(to,from,anyCan)=>{returnaxios.get(`/api/posts/${to.params.id}/publish`).then((response)=>anyCan(['is-admin',['edit-post',response.data]]));},onDeniedRoute:'/unauthorized'}}
or get the data of the defined ACL user in the evaluations by passinguser as the optionalfourth argument to the defined ACL meta function
{path:'posts/:postId/publish',component:ManagePost,meta:{anyCan:(to,from,anyCan,user)=>{returnaxios.get(`/api/users/${user.id}/posts/${to.params.id}/publish`).then((response)=>anyCan(['is-admin',['edit-post',response.data]]));},onDeniedRoute:'/unauthorized'}}
By default if you omit the 'onDeniedRoute' property from the a routes meta a denied check will redirect to a value of Vue Simple Acl'screateAcl optiononDeniedRoute which is/ by default. You can change this behaviour by setting thecreateAcl optiononDeniedRoute. This is useful if you use the package in an authentication or authorization flow by redirecting to unauthorized page if access is denied.
You can also use an object for more options (see guards section in docs):
onDeniedRoute:{ path:'/login': replace:true}
This will use replace rather than push when redirecting to the login page.
onDeniedRoute:'$from'You can set the onDeniedRoute to the special value'$from' which will return the user to wherever they came from
| Property Name | Type | Default | Description |
|---|---|---|---|
| can orallCan | string ORarray of abilities ORfunction of asynchronous evaluation:(to, from, can, user?) => {} | None | Equivalent of$can() andv-can="" |
| notCan orcanNot | string ORarray of abilities ORfunction of asynchronous evaluation:(to, from, notCan, user?) | None | Equivalent of$can.not() andv-can.not="" |
| anyCan orcanAny | string ORarray of abilities ORfunction of asynchronous evaluation:(to, from, anyCan, user?) | None | Equivalent of$can.any() andv-can.any="" |
| onDeniedRoute | string ORobject ofroute() option | Value of the default optiononDeniedRoute | A route to redirect to when `can |
Vue Simple ACL also provides some directives and methods in different verb as alias for default directive and helper function. You can use these aliases in place ofv-can directive,$can helper function and vue routercan: meta property for better semantic. See below table.
| Alias Name | Usage |
|---|---|
| Permission | As Directives:v-permission:create-postv-permission="'create-post'"v-permission.not="'create-post'"v-permission.any="['create-post', ['edit-post', post]]"In Component: $acl.permission('create-post')$acl.notPermission('create-post')$acl.anyPermission(['create-post', ['edit-post', post]])In Option API: this.$acl.permission('create-post')this.$acl.notPermission('create-post')this.$acl.anyPermission(['create-post', ['edit-post', post]])In Composition API/ setup():const acl = useAcl();acl.permission('create-post')acl.notPermission('create-post')acl.anyPermission(['create-post', ['edit-post', post]])In Vue Router meta Property:permission: 'create-post'notPermission: ['create-post', 'create-category']anyPermission: (to, from, anyPermission) => {return axios.get(`/api/posts/${to.params.id}`).then((response) => anyPermission(['create-post', ['edit-post', response.data]]));} |
| Role | As Directives:v-role:adminv-role="'admin'"v-role.not="'editor'"v-role.any="['admin', 'editor']"In Component: $acl.role('admin')$acl.notRole('editor')$acl.anyRole(['admin', 'editor'])In Option API: this.$acl.role('admin')this.$acl.notRole('editor')this.$acl.anyRole(['admin', 'editor'])In Composition API/ setup():const acl = useAcl();acl.role('admin')acl.notRole('editor')acl.anyRole(['admin', 'editor'])In Vue Router meta Property:role: 'admin'notRole: 'editor'anyRole: ['admin', 'editor'] |
| Role Or Permission | As Directives:v-role-or-permission="['admin', 'create-post']"v-role-or-permission.not="['editor', 'create-post']"v-role-or-permission.any="['admin', 'create-post', ['edit-post', post]]"In Component: $acl.roleOrPermission(['admin', 'create-post'])$acl.notRoleOrPermission(['editor', 'create-post'])$acl.anyRoleOrPermission(['admin', 'create-post', ['edit-post', post]])In Option API: this.$acl.roleOrPermission(['admin', 'create-post'])this.$acl.notRoleOrPermission(['editor', 'create-post'])this.$acl.anyRoleOrPermission(['admin', 'create-post', ['edit-post', post]])In Composition API/ setup():const acl = useAcl();acl.roleOrPermission(['admin', 'create-post'])acl.notRoleOrPermission(['editor', 'create-post'])acl.anyRoleOrPermission(['admin', 'create-post', ['edit-post', post]])In Vue Router meta Property:roleOrPermission: ['admin', 'create-post']notRoleOrPermission: ['editor', 'create-post', 'create-category']anyRoleOrPermission: (to, from, anyRoleOrPermission) => {return axios.get(`/api/posts/${to.params.id}`).then((response) => anyRoleOrPermission(['admin', 'create-post', ['edit-post', response.data]]));} |
| User | Get the data of the defined ACL user. In Component: $acl.user; // returns user object$acl.getUser(); // returns user objectIn Option API: this.$acl.user; // returns user objectthis.$acl.getUser(); // returns user objectIn Composition API/ setup():const acl = useAcl();acl.user; // returns user objectacl.getUser(); // returns user objectIn Vue Router meta Property:Pass user as the fourth argument to the defined ACL meta functionroleOrPermission: (to, from, roleOrPermission, user) => {return axios.get(`/api/users/${user.id}/posts/${to.params.id}`).then((response) => roleOrPermission(['admin', ['edit-post', response.data]]));} |
can be a user OBJECT, FUNCTION returning a user object// or an Asynchronous function returning a PROMISE of user object, suitable for performing fetch from API.
| Option Name | Type | Required | Default | Description |
|---|---|---|---|---|
| user | object or a `function | async function/Promisereturning user object. <br> *Using Async/Promise requires instance ofvue-router, the function will be auto hooked tobeforeEach()` peroperty of vue-router.* | Yes | None |
| rules | function | Yes | None | function returning instance ofdefineAclRules() e.g() => defineAclRules((setRule) => {...} |
| directiveName | object or afunction returning user object | No | 'can' | You can set a custom directive name if the default name conflicts with other installed plugins. e.g'custom-can' then in component likev-custom-can="" |
| helperName | object or afunction returning user object | No | '$can' | You can set a custom helper name if the default name conflicts with other installed plugins. e.g'$customCan', then use in component like'$customCan()' or'$customCan.not()' |
| enableSematicAlias | boolean | No | true | You can enable or disable the sematic alias directives and methods e.gv-role,v-permission,$acl.*, etc.See Semantic Alias |
| router | vue-router | No | None | Inte |
| onDeniedRoute | string orobject | No | / | A route to redirect to whencan 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 |
- Chore: Write basic tests
- A documentation page with vitepress
- Fork this repository.
- Create new branch with feature name.
- Go to example folder and run
npm installandnpm run serve. - The plugin sources files is located in
/src/*. - Commit and set commit message with feature name.
- Push your code to your fork repository.
- Create pull request. 🙂
If you like this project, You can support me with starring ⭐ this repository,buy me a coffee orbecome a patron.
Developed byVictory Osayi with ❤️ and ☕️ from Benin City, Edo, Nigeria.
About
A simple unopinionated Vue plugin for managing user roles and permissions, access-control list (ACL) and role-based access control (RBAC).
Topics
Resources
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.