├── docs ├── .nojekyll ├── _media │ ├── logo.png │ └── favicon.ico ├── installation │ ├── quickstart.md │ ├── vue.md │ ├── configuration.md │ └── nuxt.md ├── _coverpage.md ├── _sidebar.md ├── index.html ├── usage │ ├── middlewares.md │ ├── methods.md │ └── directives.md └── README.md ├── .prettierignore ├── .gitignore ├── .vscode ├── extensions.json ├── settings.json └── launch.json ├── tsconfig.module.json ├── .github ├── CONTRIBUTING.md ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE.md ├── .editorconfig ├── src ├── utils │ ├── strings.ts │ └── validators.ts ├── types │ └── typing.d.ts ├── index.ts └── core │ └── Zo.ts ├── .cspell.json ├── LICENSE ├── .eslintrc.json ├── README.md ├── .circleci └── config.yml ├── tsconfig.json └── package.json /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/_media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thonymg/vue-zo/HEAD/docs/_media/logo.png -------------------------------------------------------------------------------- /docs/_media/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thonymg/vue-zo/HEAD/docs/_media/favicon.ico -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # package.json is formatted by package managers, so we ignore it here 2 | package.json -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | diff 2 | .idea/* 3 | .nyc_output 4 | build 5 | node_modules 6 | test 7 | src/**.js 8 | coverage 9 | *.log 10 | yarn.lock 11 | -------------------------------------------------------------------------------- /docs/installation/quickstart.md: -------------------------------------------------------------------------------- 1 | # 💿 Installation 2 | 3 | #### yarn 4 | 5 | ``` 6 | yarn add vue-zo 7 | ``` 8 | 9 | #### npm 10 | 11 | ``` 12 | npm i vue-zo --save 13 | ``` 14 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "esbenp.prettier-vscode", 5 | "eamodio.gitlens", 6 | "streetsidesoftware.code-spell-checker", 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.module.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "compilerOptions": { 4 | "target": "esnext", 5 | "outDir": "build/module", 6 | "module": "esnext" 7 | }, 8 | "exclude": [ 9 | "node_modules/**" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.userWords": [], // only use words from .cspell.json 3 | "cSpell.enabled": true, 4 | "editor.formatOnSave": true, 5 | "typescript.tsdk": "node_modules/typescript/lib", 6 | "typescript.enablePromptUseWorkspaceTsdk": true 7 | } 8 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Example Contributing Guidelines 2 | 3 | This is an example of GitHub's contributing guidelines file. Check out GitHub's [CONTRIBUTING.md help center article](https://help.github.com/articles/setting-guidelines-for-repository-contributors/) for more information. 4 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | - **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...) 2 | 3 | - **What is the current behavior?** (You can also link to an open issue here) 4 | 5 | - **What is the new behavior (if this is a feature change)?** 6 | 7 | - **Other information**: 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | max_line_length = 80 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | max_line_length = 0 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /docs/_coverpage.md: -------------------------------------------------------------------------------- 1 | ![logo](_media/logo.png) 2 | 3 | # Vue Zo 4 | 5 | > Protecting every thing 6 | 7 | - Powerful and lightweight 8 | - Directives, methods and more! 9 | - TypeScript support 10 | - Nuxt.js support 11 | 12 | [GitHub](https://github.com/thonymg/vue-zo) 13 | [Getting Started](/installation/quickstart) 14 | 15 | 16 | 17 | ![color](#f0f0f0) 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | - **I'm submitting a ...** 2 | [ ] bug report 3 | [ ] feature request 4 | [ ] question about the decisions made in the repository 5 | [ ] question about how to use this project 6 | 7 | - **Summary** 8 | 9 | - **Other information** (e.g. detailed explanation, stack traces, related issues, suggestions how to fix, links for us to have context, eg. StackOverflow, personal fork, etc.) 10 | -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | - Getting started 2 | 3 | - [Quick start](installation/quickstart.md) 4 | - [Vue.js](installation/vue.md) 5 | - [Nuxt.js](installation/nuxt.md) 6 | - [Configuration](installation/configuration.md) 7 | 8 | - Usage 9 | 10 | - [Directives](usage/directives.md) 11 | - [Middlewares](usage/middlewares.md) 12 | - [Methods](usage/methods.md) 13 | - [Server Side Rendering](usage/ssr.md) 14 | -------------------------------------------------------------------------------- /src/utils/strings.ts: -------------------------------------------------------------------------------- 1 | export const startCase = (str: string) => 2 | `${str.charAt(0).toUpperCase()}${str.slice(1)}`; 3 | 4 | export const isEmpty = (obj: string) => Object.keys(obj).length === 0; 5 | 6 | export const pregQuote = (str: string) => 7 | str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&'); 8 | 9 | export const match = (str: string, wildcard: string) => { 10 | const regex = new RegExp( 11 | `^${wildcard.split(/\*+/).map(pregQuote).join('.*')}$`, 12 | 'g' 13 | ); 14 | return str.match(regex); 15 | }; 16 | -------------------------------------------------------------------------------- /docs/installation/vue.md: -------------------------------------------------------------------------------- 1 | # 👌 Install in Vue 3 2 | 3 | In your script entry point: 4 | 5 | **Add plugin:** 6 | 7 | ```typescript 8 | import { createApp } from 'vue' 9 | import { VueZo, ZoOption } from 'vue-zo' 10 | import App from './App.vue' 11 | 12 | 13 | declare class VueZo { 14 | static install: (app: any, options?: ZoOption) => void; 15 | } 16 | 17 | createApp(App) 18 | .use(VueZo) 19 | .mount('#app') 20 | ``` 21 | 22 | # 🤟 Install in Vue 2 23 | 24 | In your script entry point: 25 | 26 | **Add plugin:** 27 | 28 | ```javascript 29 | import Vue from 'vue'; 30 | import {VueZo }from 'vue-zo'; 31 | 32 | Vue.use(VueZo); 33 | ``` 34 | 35 | *That's all!* 36 | -------------------------------------------------------------------------------- /.cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1", 3 | "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/master/cspell.schema.json", 4 | "language": "en", 5 | "words": [ 6 | "bitjson", 7 | "bitauth", 8 | "cimg", 9 | "circleci", 10 | "codecov", 11 | "commitlint", 12 | "dependabot", 13 | "editorconfig", 14 | "esnext", 15 | "execa", 16 | "exponentiate", 17 | "globby", 18 | "libauth", 19 | "mkdir", 20 | "prettierignore", 21 | "sandboxed", 22 | "transpiled", 23 | "typedoc", 24 | "untracked" 25 | ], 26 | "flagWords": [], 27 | "ignorePaths": [ 28 | "package.json", 29 | "package-lock.json", 30 | "yarn.lock", 31 | "tsconfig.json", 32 | "node_modules/**" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /src/types/typing.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-types */ 2 | import { Zo } from '../core/Zo'; 3 | 4 | export type ZoTypes = { 5 | getPermissions: Function; 6 | getRoles: Function; 7 | hasAllPermissions: Function; 8 | hasAllRoles: Function; 9 | hasAnyPermission: Function; 10 | hasAnyRole: Function; 11 | hasPermission: Function; 12 | hasRole: Function; 13 | isSuperUser: Function; 14 | setPermissions: Function; 15 | setRoles: Function; 16 | unlessPermission: Function; 17 | unlessRole: Function; 18 | }; 19 | 20 | export type ZoOption = { 21 | persistent?: boolean | undefined; 22 | superRole?: string | undefined; 23 | }; 24 | 25 | export type Binding = { 26 | name: string; 27 | arg: string; 28 | value: string; 29 | modifiers: string; 30 | }; 31 | 32 | export interface ZoVueElement { 33 | $zo: Zo; 34 | directive: Function; 35 | config: any; 36 | provide: any; 37 | prototype: any; 38 | } 39 | -------------------------------------------------------------------------------- /docs/installation/configuration.md: -------------------------------------------------------------------------------- 1 | # 🔧 Configuration 2 | 3 | This packages has some useful options: 4 | 5 | | Name | Description | Default | 6 | | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | 7 | | superRole | Super role avoids all role and permission validations. | `null` | 8 | | persistent | If the value is `true` then it will save the roles and permissions in `localStorage`, when the application reloads the values will be set automatically. | `false` | 9 | 10 | ```javascript 11 | import Vue from 'vue'; 12 | import {VueZo} from 'vue-zo'; 13 | 14 | Vue.use(VueZo, { 15 | // Configuration 16 | }); 17 | ``` 18 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | // To debug, make sure a *.spec.ts file is active in the editor, then run a configuration 5 | { 6 | "type": "node", 7 | "request": "launch", 8 | "name": "Debug Active Spec", 9 | "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/ava", 10 | "runtimeArgs": ["debug", "--break", "--serial", "${file}"], 11 | "port": 9229, 12 | "outputCapture": "std", 13 | "skipFiles": ["/**/*.js"], 14 | "preLaunchTask": "npm: build" 15 | // "smartStep": true 16 | }, 17 | { 18 | // Use this one if you're already running `yarn watch` 19 | "type": "node", 20 | "request": "launch", 21 | "name": "Debug Active Spec (no build)", 22 | "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/ava", 23 | "runtimeArgs": ["debug", "--break", "--serial", "${file}"], 24 | "port": 9229, 25 | "outputCapture": "std", 26 | "skipFiles": ["/**/*.js"] 27 | // "smartStep": true 28 | }] 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Anthony Michel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "project": "./tsconfig.json" 6 | }, 7 | "env": { 8 | "es6": true 9 | }, 10 | "ignorePatterns": [ 11 | "node_modules", 12 | "build", 13 | "coverage" 14 | ], 15 | "plugins": [ 16 | "import", 17 | "eslint-comments" 18 | ], 19 | "extends": [ 20 | "eslint:recommended", 21 | "plugin:eslint-comments/recommended", 22 | "plugin:@typescript-eslint/recommended", 23 | "plugin:import/typescript", 24 | "prettier", 25 | "prettier/@typescript-eslint" 26 | ], 27 | "globals": { 28 | "BigInt": true, 29 | "console": true, 30 | "WebAssembly": true 31 | }, 32 | "rules": { 33 | "@typescript-eslint/explicit-module-boundary-types": "off", 34 | "eslint-comments/disable-enable-pair": [ 35 | "error", 36 | { 37 | "allowWholeFile": true 38 | } 39 | ], 40 | "eslint-comments/no-unused-disable": "error", 41 | "sort-imports": [ 42 | "error", 43 | { 44 | "ignoreDeclarationSort": true, 45 | "ignoreCase": true 46 | } 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue Zo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /docs/usage/middlewares.md: -------------------------------------------------------------------------------- 1 | # 👮‍♀️ Middlewares 2 | 3 | Based in the [official Nuxt documentation](https://nuxtjs.org/api/pages-middleware/). 4 | 5 | ## Named middleware 6 | 7 | You can create named middleware by creating a file inside the `middleware/` directory, the file name will be the middleware name: 8 | 9 | `middleware/admin.js`: 10 | ```javascript 11 | export default function ({ $zo, redirect }) { 12 | // If the user is not an admin 13 | if (!$zo.hasRole('admin')) { 14 | return redirect('/login') 15 | } 16 | } 17 | ``` 18 | 19 | `pages/secret.vue`: 20 | ```vue 21 | 24 | 25 | 30 | ``` 31 | 32 | ## Anonymous middleware 33 | 34 | If you need to use a middleware only for a specific page, you can directly use a function for it (or an array of functions): 35 | 36 | `pages/secret.vue`: 37 | ```vue 38 | 41 | 42 | 52 | ``` 53 | -------------------------------------------------------------------------------- /docs/installation/nuxt.md: -------------------------------------------------------------------------------- 1 | # 📐 Install in Nuxt.js 2 | 3 | #### Nuxt 2 4 | 5 | In your Nuxt2 project: 6 | 7 | **1. Create plugin:** 8 | 9 | `~/plugins/vue-zo.js`: 10 | 11 | ```javascript 12 | import Vue from 'vue'; 13 | import { VueZo } from 'vue-zo'; 14 | 15 | Vue.use(VueZo); 16 | 17 | export default (_context, inject) => { 18 | inject('zo', Vue.prototype.$zo); 19 | }; 20 | ``` 21 | 22 | **2. Then register it:** 23 | 24 | `nuxt.config.js`: 25 | 26 | ```javascript 27 | export default { 28 | plugins: ['~/plugins/vue-zo'], 29 | }; 30 | ``` 31 | 32 | #### Nuxt3 33 | 34 | **1. Create plugin:** 35 | 36 | `~/plugins/vue-zo.ts`: 37 | 38 | ```typescript 39 | import { VueZo } from 'vue-zo'; 40 | 41 | declare module '@vue/runtime-core' { 42 | interface App { 43 | $zo: typeof VueZo; 44 | } 45 | } 46 | 47 | export default defineNuxtPlugin((nuxtApp) => { 48 | const { $zo } = nuxtApp.vueApp.use(VueZo, {}); 49 | nuxtApp.provide('zo', $zo); 50 | }); 51 | ``` 52 | 53 | **2. Then use it:** 54 | 55 | `app.vue`: 56 | 57 | ```typescript 58 | 63 | 64 | 72 | ``` 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

Vue Zo - Roles & Permissions

3 | 4 |

5 | Version 6 | Vue 7 | Vue 8 | Downloads 9 | License 10 |

11 | 12 | vue-zo is a plugin for [Vue.js](https://vuejs.org/) & [Nuxt.js](https://nuxtjs.org/) that allows you to use roles and permissions in your components (setup macro & option API), also compatible as middleware, methods, composition-api and [pinia.js](https://pinia.vuejs.org/). 13 | 14 | ## Installation 15 | 16 | ```cmd 17 | npm install vue-zo --save-dev 18 | ``` 19 | ## Features 20 | 21 | - Persistent roles/permissions 22 | - Super role avoids all role and permission validations 23 | - Directives 24 | - Middlewares 25 | - Methods 26 | - Wildcard support 27 | - Support server-side rendering (Nuxt.js) 28 | - TypeScript support 29 | 30 | ## documentation 31 | [Docs](https://github.com/thonymg/vue-zo/tree/master/docs) 32 | 33 | ## 🙈 Credits 34 | 35 | - Based on [Vue-gates](https://github.com/williamcruzme/vue-gates). 36 | 37 | ## 🔒 License 38 | 39 | MIT 40 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | 2 |

Vue Zo - Roles & Permissions

3 | 4 |

5 | Version 6 | Vue 7 | Vue 8 | Downloads 9 | License 10 |

11 | 12 | vue-zo is a plugin for [Vue.js](https://vuejs.org/) & [Nuxt.js](https://nuxtjs.org/) that allows you to use roles and permissions in your components (setup macro & option API), also compatible as middleware, methods, composition-api and [pinia.js](https://pinia.vuejs.org/). 13 | 14 | ## Installation 15 | 16 | ```cmd 17 | npm install vue-zo --save-dev 18 | ``` 19 | ## Features 20 | 21 | - Persistent roles/permissions 22 | - Super role avoids all role and permission validations 23 | - Directives 24 | - Middlewares 25 | - Methods 26 | - Wildcard support 27 | - Support server-side rendering (Nuxt.js) 28 | - TypeScript support 29 | 30 | ## documentation 31 | [Docs](https://github.com/thonymg/vue-zo/tree/master/docs) 32 | 33 | ## 🙈 Credits 34 | 35 | - Based on [Vue-gates](https://github.com/williamcruzme/vue-gates). 36 | 37 | ## 🔒 License 38 | 39 | MIT 40 | -------------------------------------------------------------------------------- /src/utils/validators.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-types */ 2 | import { Binding, ZoVueElement } from '../types/typing'; 3 | import { isEmpty, startCase } from './strings'; 4 | 5 | export const parseCondition = (binding: Binding): string => { 6 | let suffix = binding.name === 'can' ? 'permission' : binding.name; 7 | let arg = 'has'; 8 | 9 | if (binding.arg) { 10 | if (binding.arg === 'unless') { 11 | arg = 'unless'; 12 | } else if (binding.arg !== 'has') { 13 | arg += startCase(binding.arg); 14 | } 15 | } 16 | 17 | // Convert to plural if is needed 18 | if (arg === 'hasAll') { 19 | suffix += 's'; 20 | } 21 | 22 | return `${arg}${startCase(suffix)}`; 23 | }; 24 | 25 | export const isConditionPassed = 26 | (app: ZoVueElement, condition: string | Function) => 27 | (el: HTMLElement, binding: Binding) => { 28 | if (!binding.value) { 29 | return; 30 | } 31 | 32 | // Get condition to validate 33 | let isValid = false; 34 | if (typeof condition === 'function') { 35 | isValid = condition(binding); 36 | } else { 37 | binding.name = condition; 38 | // Fix missing name property 39 | isValid = app.$zo[parseCondition(binding)](binding.value); 40 | } 41 | 42 | if (!isValid) { 43 | if (isEmpty(binding.modifiers)) { 44 | // Remove DOM Element 45 | el.parentNode?.removeChild(el); 46 | } else { 47 | // Set attributes to DOM element 48 | Object.assign(el, binding.modifiers); 49 | } 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /docs/usage/methods.md: -------------------------------------------------------------------------------- 1 | # ⚔ Methods 2 | 3 | ## Set roles and permissions 4 | 5 | This should be the first step. When you log in or start the application, you must set the roles and permissions: 6 | 7 | 8 | ### Option API 9 | ```javascript 10 | this.$zo.setRoles(['writer']); 11 | this.$zo.setPermissions(['posts.*', 'images.create']); 12 | 13 | this.$zo.getRoles(); // ['writer'] 14 | this.$zo.getPermissions(); // ['posts.*', 'images.create'] 15 | ``` 16 | 17 | ### Setup macro 18 | ```javascript 19 | import { inject } from 'vue' 20 | 21 | const $zo = inject("$zo"); 22 | 23 | $zo.setRoles(['writer']); 24 | $zo.setPermissions(['posts.*', 'images.create']); 25 | 26 | $zo.getRoles(); // ['writer'] 27 | $zo.getPermissions(); // ['posts.*', 'images.create'] 28 | ``` 29 | 30 | ### Pinia Store 31 | ```javascript 32 | import { inject } from 'vue' 33 | import { defineStore } from "pinia"; 34 | 35 | export const useAPI = defineStore("api", () => { 36 | let $zo: ZoTypes; 37 | 38 | const setRoles = () => { 39 | $zo.setRoles(['writer']); 40 | $zo.setPermissions(['posts.*', 'images.create']); 41 | } 42 | 43 | 44 | onMounted(() => { 45 | $zo = inject("$zo"); 46 | }); 47 | 48 | return { 49 | setRoles 50 | }; 51 | }) 52 | 53 | ``` 54 | 55 | 56 | ## Directives as functions 57 | 58 | You can also use the custom directives as functions. 59 | 60 | ```javascript 61 | this.$zo.hasRole('admin'); // false 62 | this.$zo.unlessRole('admin'); // true 63 | this.$zo.hasAnyRole('admin|writer'); // true 64 | this.$zo.hasAllRoles('admin|writer'); // false 65 | 66 | this.$zo.hasPermission('posts.create'); // true 67 | this.$zo.unlessPermission('posts.create'); // false 68 | this.$zo.hasAnyPermission('posts.create|images'); // true 69 | this.$zo.hasAllPermissions('posts.create|images.create'); // true 70 | ``` 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Binding, ZoVueElement } from './types/typing'; 2 | 3 | import { isConditionPassed } from './utils/validators'; 4 | import { Zo } from './core/Zo'; 5 | 6 | const registerDirectives = (app: ZoVueElement, newSyntax = false) => { 7 | const lifecycleName = newSyntax ? 'mounted' : 'inserted'; 8 | 9 | const directiveOptions = (condition: string) => [ 10 | condition, 11 | { 12 | [lifecycleName]: isConditionPassed(app, condition), 13 | }, 14 | ]; 15 | 16 | app.directive(...directiveOptions('role')); 17 | app.directive(...directiveOptions('permission')); 18 | app.directive(...directiveOptions('can')); // Alias for "v-permission" 19 | app.directive('role-or-permission', { 20 | [lifecycleName]: isConditionPassed(app, (binding: Binding) => { 21 | const values = binding.value.split('|'); 22 | const role = values[0]; 23 | const permission = values[1]; 24 | 25 | return app.$zo.hasRole(role) || app.$zo.hasPermission(permission); 26 | }), 27 | }); 28 | }; 29 | 30 | const registerOnVue2 = (app: ZoVueElement, instance: Zo) => { 31 | Object.defineProperty(app.prototype, '$zo', { 32 | get() { 33 | return instance; 34 | }, 35 | }); 36 | app.$zo = instance; 37 | registerDirectives(app, false); 38 | }; 39 | 40 | const registerOnVue3 = (app: ZoVueElement, instance: Zo) => { 41 | app.config.globalProperties.$zo = instance; 42 | app.$zo = instance; 43 | 44 | app.provide('$zo', instance); 45 | registerDirectives(app, true); 46 | }; 47 | 48 | export const VueZo = { 49 | install(Vue: any, options = {}) { 50 | const isVue3 = !!Vue.config.globalProperties; 51 | const zo = new Zo(options); 52 | 53 | if (isVue3) { 54 | registerOnVue3(Vue, zo); 55 | } else { 56 | registerOnVue2(Vue, zo); 57 | } 58 | }, 59 | }; 60 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # https://circleci.com/docs/2.0/language-javascript/ 2 | version: 2 3 | jobs: 4 | 'node-10': 5 | docker: 6 | - image: circleci/node:10 7 | steps: 8 | - checkout 9 | # Download and cache dependencies 10 | - restore_cache: 11 | keys: 12 | - v1-dependencies-{{ checksum "package.json" }} 13 | # fallback to using the latest cache if no exact match is found 14 | - v1-dependencies- 15 | - run: npm install 16 | - save_cache: 17 | paths: 18 | - node_modules 19 | key: v1-dependencies-{{ checksum "package.json" }} 20 | - run: npm test 21 | - run: npm run check-integration-tests 22 | - run: npm run cov:send 23 | - run: npm run cov:check 24 | 'node-12': 25 | docker: 26 | - image: circleci/node:12 27 | steps: 28 | - checkout 29 | - restore_cache: 30 | keys: 31 | - v1-dependencies-{{ checksum "package.json" }} 32 | - v1-dependencies- 33 | - run: npm install 34 | - save_cache: 35 | paths: 36 | - node_modules 37 | key: v1-dependencies-{{ checksum "package.json" }} 38 | - run: npm test 39 | - run: npm run check-integration-tests 40 | - run: npm run cov:send 41 | - run: npm run cov:check 42 | 'node-latest': 43 | docker: 44 | - image: circleci/node:latest 45 | steps: 46 | - checkout 47 | - restore_cache: 48 | keys: 49 | - v1-dependencies-{{ checksum "package.json" }} 50 | - v1-dependencies- 51 | - run: npm install 52 | - save_cache: 53 | paths: 54 | - node_modules 55 | key: v1-dependencies-{{ checksum "package.json" }} 56 | - run: npm test 57 | - run: npm run check-integration-tests 58 | - run: npm run cov:send 59 | - run: npm run cov:check 60 | 61 | workflows: 62 | version: 2 63 | build: 64 | jobs: 65 | - 'node-10' 66 | - 'node-12' 67 | - 'node-latest' 68 | -------------------------------------------------------------------------------- /docs/usage/directives.md: -------------------------------------------------------------------------------- 1 | # ✨ Directives 2 | 3 | The directive will validate the roles and permissions. It will create or destroy the component or DOM element whether or not it meets the condition 😎 4 | 5 | ## Roles 6 | 7 | Check for a specific role: 8 | 9 | ```vue 10 | 11 | ``` 12 | 13 | Check for any role in a list: 14 | 15 | ```vue 16 | 17 | ``` 18 | 19 | Check for all roles: 20 | 21 | ```vue 22 | 23 | ``` 24 | 25 | Check for unless role: 26 | 27 | ```vue 28 |

You are not an Super Admin!

29 | ``` 30 | 31 | ## Permissions 32 | 33 | Check for a specific permission: 34 | 35 | ```vue 36 | 37 | 38 | 39 | 40 | ``` 41 | 42 | Check for any permission in a list: 43 | 44 | ```vue 45 | 46 | ``` 47 | 48 | Check for all permissions: 49 | 50 | ```vue 51 | 52 | ``` 53 | 54 | Check for unless permission: 55 | 56 | ```vue 57 |

You dont have permission!

58 | ``` 59 | 60 | ## Roles & Permissions 61 | 62 | Check for role and permission: 63 | 64 | ```vue 65 | 66 | 67 | 68 | ``` 69 | 70 | Check for role or permission: 71 | 72 | ```vue 73 | 74 | ``` 75 | 76 | ## Working with attributes 77 | 78 | You can also set `true` to any attribute of DOM element if the condition is not met. You can set multiple attributes. 79 | 80 | ```vue 81 | 82 | 83 | 84 | 85 | ``` 86 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, 4 | "target": "es2017", 5 | "outDir": "build/main", 6 | "rootDir": "src", 7 | "moduleResolution": "node", 8 | "module": "commonjs", 9 | "declaration": true, 10 | "inlineSourceMap": true, 11 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 12 | "resolveJsonModule": true /* Include modules imported with .json extension. */, 13 | 14 | // "strict": true /* Enable all strict type-checking options. */, 15 | 16 | /* Strict Type-Checking Options */ 17 | // "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, 18 | // "strictNullChecks": true /* Enable strict null checks. */, 19 | // "strictFunctionTypes": true /* Enable strict checking of function types. */, 20 | // "strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */, 21 | // "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */, 22 | // "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */, 23 | 24 | /* Additional Checks */ 25 | "noUnusedLocals": true /* Report errors on unused locals. */, 26 | "noUnusedParameters": true /* Report errors on unused parameters. */, 27 | "noImplicitReturns": true /* Report error when not all code paths in function return a value. */, 28 | "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */, 29 | 30 | /* Debugging Options */ 31 | "traceResolution": false /* Report module resolution log messages. */, 32 | "listEmittedFiles": false /* Print names of generated files part of the compilation. */, 33 | "listFiles": false /* Print names of files part of the compilation. */, 34 | "pretty": true /* Stylize errors and messages using color and context. */, 35 | 36 | /* Experimental Options */ 37 | // "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, 38 | // "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */, 39 | 40 | "lib": ["es2017", "dom"], 41 | "types": ["node"], 42 | "typeRoots": ["node_modules/@types", "src/types"] 43 | }, 44 | "include": ["src/**/*.ts"], 45 | "exclude": ["node_modules/**"], 46 | "compileOnSave": false 47 | } 48 | -------------------------------------------------------------------------------- /src/core/Zo.ts: -------------------------------------------------------------------------------- 1 | import { match } from '../utils/strings'; 2 | import { ZoOption } from './../types/typing.d'; 3 | 4 | export class Zo { 5 | #canPersistent = false; 6 | 7 | #roles: string[] = []; 8 | 9 | #permissions: string[] = []; 10 | 11 | #superRole: undefined | string = undefined; 12 | 13 | constructor(options: ZoOption = {}) { 14 | const canPersistent = options.persistent; 15 | const roles = canPersistent 16 | ? JSON.parse(localStorage.getItem('roles')!) 17 | : []; 18 | const permissions = canPersistent 19 | ? JSON.parse(localStorage.getItem('permissions')!) 20 | : []; 21 | 22 | this.#canPersistent = canPersistent; 23 | this.#roles = roles; 24 | this.#permissions = permissions; 25 | this.#superRole = options.superRole; 26 | } 27 | 28 | /* 29 | |------------------------------------------------------------------------- 30 | | Setters 31 | |------------------------------------------------------------------------- 32 | | 33 | | These functions controls the "permissions" and "roles" provided 34 | | by Vue Gates, or from a custom array. 35 | | 36 | */ 37 | 38 | setRoles = (roles: string[]) => { 39 | this.#roles = roles; 40 | if (this.#canPersistent) { 41 | localStorage.setItem('roles', JSON.stringify(roles)); 42 | } 43 | }; 44 | 45 | setPermissions = (permissions: string[]) => { 46 | this.#permissions = permissions; 47 | if (this.#canPersistent) { 48 | localStorage.setItem('permissions', JSON.stringify(permissions)); 49 | } 50 | }; 51 | 52 | /* 53 | |------------------------------------------------------------------------- 54 | | Getters 55 | |------------------------------------------------------------------------- 56 | | 57 | | These functions return the "permissions" and "roles" stored. 58 | | This is useful when you want list all data. 59 | | 60 | */ 61 | 62 | getRoles = () => this.#roles; 63 | 64 | getPermissions = () => this.#permissions; 65 | 66 | isSuperUser = () => this.#superRole && this.#roles.includes(this.#superRole); 67 | 68 | /* 69 | |------------------------------------------------------------------------- 70 | | Directives 71 | |------------------------------------------------------------------------- 72 | | 73 | | These is a group of functions for Vue Directives. 74 | | This is useful when you want valid a "permission" or "role" 75 | | programmatically. 76 | | 77 | */ 78 | 79 | // Roles 80 | hasRole = (role: string | string[]) => 81 | this.isSuperUser() || this.#roles.includes(role as string); 82 | 83 | unlessRole = (role: string | string[]) => !this.hasRole(role); 84 | 85 | hasAnyRole = (values: string) => { 86 | if (this.isSuperUser()) { 87 | return true; 88 | } 89 | 90 | const roles = values.split('|'); 91 | return roles.some((role) => this.hasRole(role)); 92 | }; 93 | 94 | hasAllRoles = (values: string) => { 95 | if (this.isSuperUser()) { 96 | return true; 97 | } 98 | 99 | const roles = values.split('|'); 100 | return roles.every((role) => this.hasRole(role)); 101 | }; 102 | 103 | // Permissions 104 | hasPermission = (permission: string | string[]) => 105 | this.isSuperUser() || 106 | !!this.#permissions.find((wildcard) => 107 | match(permission as string, wildcard) 108 | ); 109 | 110 | unlessPermission = (permission: string | string[]) => 111 | !this.hasPermission(permission); 112 | 113 | hasAnyPermission = (values: string) => { 114 | if (this.isSuperUser()) { 115 | return true; 116 | } 117 | 118 | const permissions = values.split('|'); 119 | return permissions.some((permission) => this.hasPermission(permission)); 120 | }; 121 | 122 | hasAllPermissions = (values: string) => { 123 | if (this.isSuperUser()) { 124 | return true; 125 | } 126 | 127 | const permissions = values.split('|'); 128 | return permissions.every((permission) => this.hasPermission(permission)); 129 | }; 130 | } 131 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-zo", 3 | "version": "0.1.20", 4 | "description": "A Vuejs/Nuxt plugin that allows you to use roles and permissions in your components (setup macro & option API), also compatible as middleware, methods, composition-api and Pinia", 5 | "main": "build/main/index.js", 6 | "typings": "build/main/index.d.ts", 7 | "module": "build/module/index.js", 8 | "license": "MIT", 9 | "author": "Anthony Michel (@thonymg)", 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/thonymg/vue-zo.git" 13 | }, 14 | "keywords": ["vue2" , "vue3", "nuxt2", "nuxt3", "pinia", "permissions", "roles", "plugin", "composition-api"], 15 | "bugs": { 16 | "url": "https://github.com/thonymg/vue-zo/issues" 17 | }, 18 | "homepage": "https://github.com/thonymg/vue-zo#readme", 19 | "scripts": { 20 | "build": "run-p build:*", 21 | "build:main": "tsc -p tsconfig.json", 22 | "build:module": "tsc -p tsconfig.module.json", 23 | "fix": "run-s fix:*", 24 | "fix:prettier": "prettier \"src/**/*.ts\" --write", 25 | "fix:lint": "eslint src --ext .ts --fix", 26 | "test": "run-s build test:*", 27 | "test:lint": "eslint src --ext .ts", 28 | "test:prettier": "prettier \"src/**/*.ts\" --list-different", 29 | "test:spelling": "cspell \"{README.md,.github/*.md,src/**/*.ts}\"", 30 | "test:unit": "nyc --silent ava", 31 | "check-cli": "run-s test diff-integration-tests check-integration-tests", 32 | "check-integration-tests": "run-s check-integration-test:*", 33 | "diff-integration-tests": "mkdir -p diff && rm -rf diff/test && cp -r test diff/test && rm -rf diff/test/test-*/.git && cd diff && git init --quiet && git add -A && git commit --quiet --no-verify --allow-empty -m 'WIP' && echo '\\n\\nCommitted most recent integration test output in the \"diff\" directory. Review the changes with \"cd diff && git diff HEAD\" or your preferred git diff viewer.'", 34 | "watch:build": "tsc -p tsconfig.json -w", 35 | "watch:test": "nyc --silent ava --watch", 36 | "cov": "run-s build test:unit cov:html cov:lcov && open-cli coverage/index.html", 37 | "cov:html": "nyc report --reporter=html", 38 | "cov:lcov": "nyc report --reporter=lcov", 39 | "cov:send": "run-s cov:lcov && codecov", 40 | "cov:check": "nyc report && nyc check-coverage --lines 100 --functions 100 --branches 100", 41 | "doc": "run-s doc:html && open-cli build/docs/index.html", 42 | "doc:html": "typedoc src/ --exclude **/*.spec.ts --target ES6 --mode file --out build/docs", 43 | "doc:json": "typedoc src/ --exclude **/*.spec.ts --target ES6 --mode file --json build/docs/typedoc.json", 44 | "doc:publish": "gh-pages -m \"[ci skip] Updates\" -d build/docs", 45 | "version": "standard-version", 46 | "reset-hard": "git clean -dfx && git reset --hard && npm i", 47 | "prepare-release": "run-s reset-hard test cov:check doc:html version doc:publish" 48 | }, 49 | "engines": { 50 | "node": ">=10" 51 | }, 52 | "dependencies": { 53 | }, 54 | "devDependencies": { 55 | "@ava/typescript": "^1.1.1", 56 | "@istanbuljs/nyc-config-typescript": "^1.0.1", 57 | "@typescript-eslint/eslint-plugin": "^4.0.1", 58 | "@typescript-eslint/parser": "^4.0.1", 59 | "ava": "^3.12.1", 60 | "codecov": "^3.5.0", 61 | "cspell": "^4.1.0", 62 | "cz-conventional-changelog": "^3.3.0", 63 | "eslint": "^7.8.0", 64 | "eslint-config-prettier": "^6.11.0", 65 | "eslint-plugin-eslint-comments": "^3.2.0", 66 | "eslint-plugin-functional": "^3.0.2", 67 | "eslint-plugin-import": "^2.22.0", 68 | "gh-pages": "^3.1.0", 69 | "npm-run-all": "^4.1.5", 70 | "nyc": "^15.1.0", 71 | "open-cli": "^6.0.1", 72 | "prettier": "^2.1.1", 73 | "standard-version": "^9.0.0", 74 | "ts-node": "^9.0.0", 75 | "typedoc": "^0.19.0", 76 | "typescript": "^4.0.2" 77 | }, 78 | "files": [ 79 | "build/main", 80 | "build/module", 81 | "!**/*.spec.*", 82 | "!**/*.json", 83 | "CHANGELOG.md", 84 | "LICENSE", 85 | "README.md" 86 | ], 87 | "ava": { 88 | "failFast": true, 89 | "timeout": "60s", 90 | "typescript": { 91 | "rewritePaths": { 92 | "src/": "build/main/" 93 | } 94 | }, 95 | "files": [ 96 | "!build/module/**" 97 | ] 98 | }, 99 | "config": { 100 | "commitizen": { 101 | "path": "cz-conventional-changelog" 102 | } 103 | }, 104 | "prettier": { 105 | "singleQuote": true 106 | }, 107 | "nyc": { 108 | "extends": "@istanbuljs/nyc-config-typescript", 109 | "exclude": [ 110 | "**/*.spec.js" 111 | ] 112 | } 113 | } 114 | --------------------------------------------------------------------------------