UNPKG

vue-nuxt-permission

Version:

A lightweight, flexible permission directive and router guard for Vue 3

595 lines (467 loc) • 15.8 kB
# šŸ” vue-nuxt-permission A powerful, flexible permission management plugin for Vue 3 & Nuxt 3 Provides a declarative directive (`v-permission`), route protection (`globalGuard`), permission evaluation utilities, Base64-encoded localStorage persistence, and caching. [![GitHub](https://img.shields.io/badge/GitHub-vue--nuxt--permission-blue?style=flat&logo=github)](https://github.com/keroloszakaria/vue-nuxt-permission) [![License](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) [![Vue](https://img.shields.io/badge/Vue-3.x-4FC08D?style=flat&logo=vue.js)](https://vuejs.org/) [![Nuxt](https://img.shields.io/badge/Nuxt-3.x-00DC82?style=flat&logo=nuxt.js)](https://nuxt.com/) > šŸ“¦ **Package**: [npm](https://www.npmjs.com/package/vue-nuxt-permission) | šŸ“š **Source**: [GitHub](https://github.com/keroloszakaria/vue-nuxt-permission) | šŸ› **Issues**: [GitHub Issues](https://github.com/keroloszakaria/vue-nuxt-permission/issues) --- ## šŸš€ Features - āœ… **`v-permission` directive**: show/hide/remove elements based on permissions - šŸ›”ļø **Route guard** `globalGuard` with fallback redirection - ⚔ **Performance**: Fast with permission caching - šŸ”€ **Complex logic**: Supports `and`, `or`, `regex`, `startWith`, `exact`, etc. - šŸ”’ **Security**: Stores permissions in Base64-encoded `localStorage` - šŸ”§ **Flexible**: Simple API & customizable behavior - šŸ“± **Reactive**: Works with both static arrays or reactive `Ref<string[]>` - šŸŽÆ **TypeScript**: Full TypeScript support --- ## šŸ“¦ Installation ```bash npm install vue-nuxt-permission # or yarn add vue-nuxt-permission # or pnpm add vue-nuxt-permission ``` --- ## 🧩 Quick Setup ### Vue 3 App ```ts // main.ts import { createApp } from "vue"; import App from "./App.vue"; import { PermissionPlugin } from "vue-nuxt-permission"; const app = createApp(App); app.use(PermissionPlugin, { permissions: ["user.create", "user.view", "admin.panel"], developmentMode: process.env.NODE_ENV === "development", }); app.mount("#app"); ``` ### Nuxt 3 ```ts // plugins/vue-nuxt-permission.client.ts import { defineNuxtPlugin } from "#app"; import { PermissionPlugin } from "vue-nuxt-permission"; export default defineNuxtPlugin((nuxtApp) => { // Get permissions from your auth store/API const permissions = ["user.view", "user.edit"]; nuxtApp.vueApp.use(PermissionPlugin, { permissions, developmentMode: process.env.NODE_ENV === "development", }); }); ``` --- ## šŸŽÆ Using `v-permission` Directive ### Basic Usage ```vue <template> <!-- Remove element if permission is missing --> <button v-permission="'user.create'">Create User</button> <!-- Multiple permissions (OR logic) --> <button v-permission="['user.edit', 'user.update']">Edit User</button> <!-- Hide instead of remove --> <div v-permission:show="'admin.panel'">Admin Panel</div> </template> ``` ### Advanced Configuration ```vue <template> <!-- Require ALL permissions (AND logic) --> <button v-permission="{ permissions: ['user.edit', 'admin.users'], mode: 'and' }" > Advanced Edit </button> <!-- Regex pattern matching --> <div v-permission="{ permissions: ['^admin\\..*'], mode: 'regex' }"> Any Admin Permission </div> <!-- Exact match only --> <button v-permission="{ permissions: ['user'], mode: 'exact' }"> Exact Match </button> <!-- Start with pattern --> <nav v-permission="{ permissions: ['menu.'], mode: 'startWith' }"> Navigation Menu </nav> </template> ``` ### Directive Modifiers | Modifier | Description | Example | | -------- | --------------------------------------------- | --------------------------------- | | `:show` | Hide with `display: none` instead of removing | `v-permission:show="'user.view'"` | | `.once` | Check permission only once on mount | `v-permission.once="'user.view'"` | | `.lazy` | Don't react to permission changes | `v-permission.lazy="'user.view'"` | ```vue <template> <!-- Combined modifiers --> <div v-permission:show.once="'user.dashboard'"> Dashboard (hidden, checked once) </div> </template> ``` --- ## 🧭 Route Protection ### Setup Router Guard ```ts // router/index.ts import { createRouter, createWebHistory } from "vue-router"; import { globalGuard } from "vue-nuxt-permission"; const router = createRouter({ history: createWebHistory(), routes: [ { path: "/login", component: () => import("@/views/Login.vue"), meta: { isAuthRoute: true }, }, { path: "/dashboard", component: () => import("@/views/Dashboard.vue"), meta: { requiresAuth: true, checkPermission: true, permissions: ["dashboard.view"], }, }, { path: "/admin", component: () => import("@/views/Admin.vue"), meta: { requiresAuth: true, checkPermission: true, permissions: { permissions: ["admin.*"], mode: "regex" }, }, }, ], }); // Configure the guard router.beforeEach((to, from, next) => { globalGuard(to, from, next, { authRoutes: [{ path: "/login" }, { path: "/register" }], protectedRoutes: router.options.routes, getAuthState: () => { // Return your auth state const authStore = useAuthStore(); // or your auth logic return { isAuthenticated: authStore.isAuthenticated, user: authStore.user, }; }, }); }); export default router; ``` ### Route Meta Options | Property | Type | Description | | ----------------- | ---------------------------------------- | --------------------------------------------- | | `requiresAuth` | `boolean` | Redirect unauthenticated users to login | | `checkPermission` | `boolean` | Enable permission checking for this route | | `permissions` | `string \| string[] \| PermissionObject` | Required permissions | | `isAuthRoute` | `boolean` | Mark as authentication route (login/register) | ```ts // Example route configurations const routes = [ { path: "/users", component: UsersList, meta: { requiresAuth: true, checkPermission: true, permissions: ["users.view"], // Simple permission }, }, { path: "/settings", component: Settings, meta: { requiresAuth: true, checkPermission: true, permissions: ["settings.view", "admin.panel"], // OR logic }, }, { path: "/advanced-admin", component: AdvancedAdmin, meta: { requiresAuth: true, checkPermission: true, permissions: { // Complex logic permissions: ["admin.advanced", "super.user"], mode: "and", }, }, }, ]; ``` --- ## 🧠 Permission Checking Utilities ### `hasPermission()` Function ```ts import { hasPermission } from "vue-nuxt-permission"; // In component export default { async mounted() { // Simple check const canEdit = await hasPermission("user.edit"); // Multiple permissions (OR) const canAccess = await hasPermission(["user.view", "admin.panel"]); // Complex logic const isAdmin = await hasPermission({ permissions: ["admin.*"], mode: "regex", }); // Use in template this.showEditButton = canEdit; }, }; ``` ```vue <!-- In Composition API --> <script setup> import { ref, onMounted } from "vue"; import { hasPermission } from "vue-nuxt-permission"; const canCreateUser = ref(false); onMounted(async () => { canCreateUser.value = await hasPermission("user.create"); }); </script> <template> <button v-if="canCreateUser">Create User</button> </template> ``` --- ## āš™ļø Configuration & Management ### Dynamic Permission Updates ```ts import { configurePermission } from "vue-nuxt-permission"; // After login async function login(credentials) { const response = await api.login(credentials); const userPermissions = response.user.permissions; // Update permissions configurePermission(userPermissions, { developmentMode: process.env.NODE_ENV === "development", }); } // After logout function logout() { configurePermission([], { developmentMode: process.env.NODE_ENV === "development", }); } ``` ### Reactive Permissions with Pinia/Vuex ```ts // stores/auth.ts import { defineStore } from "pinia"; import { ref } from "vue"; import { configurePermission } from "vue-nuxt-permission"; export const useAuthStore = defineStore("auth", () => { const permissions = ref<string[]>([]); function setPermissions(newPermissions: string[]) { permissions.value = newPermissions; configurePermission(permissions); // Pass reactive ref } return { permissions, setPermissions }; }); ``` ### Cache Management ```ts import { clearPermissionCache } from "vue-nuxt-permission"; // Clear cache when permissions change function updateUserRole() { clearPermissionCache(); // Clear to force re-evaluation configurePermissionDirective(newPermissions); } ``` --- ## šŸŽØ Permission Modes | Mode | Description | Example | | ----------- | -------------------------------- | ------------------------------------------------------------ | | `or` | Any permission matches (default) | `['user.view', 'admin.panel']` | | `and` | All permissions required | `{ permissions: ['user.edit', 'user.delete'], mode: 'and' }` | | `exact` | Exact string match only | `{ permissions: ['admin'], mode: 'exact' }` | | `startWith` | Permission starts with pattern | `{ permissions: ['admin.'], mode: 'startWith' }` | | `endWith` | Permission ends with pattern | `{ permissions: ['.view'], mode: 'endWith' }` | | `regex` | Regular expression matching | `{ permissions: ['^admin\\..*'], mode: 'regex' }` | ### Mode Examples ```vue <template> <!-- OR: User needs either permission --> <button v-permission="['user.create', 'admin.users']">Create</button> <!-- AND: User needs both permissions --> <button v-permission="{ permissions: ['user.edit', 'user.delete'], mode: 'and' }" > Full Edit Access </button> <!-- REGEX: Any admin permission --> <div v-permission="{ permissions: ['^admin\\.'], mode: 'regex' }"> Admin Section </div> <!-- START WITH: Any menu permission --> <nav v-permission="{ permissions: ['menu.'], mode: 'startWith' }"> Menu Items </nav> </template> ``` --- ## šŸ› ļø API Reference ### Plugin Options ```ts interface PluginOptions { permissions?: string[] | Ref<string[]>; developmentMode?: boolean; } ``` ### Permission Value Types ```ts type PermissionValue = | string // Single permission | string[] // Multiple permissions (OR) | PermissionObject; // Complex permission object interface PermissionObject { permissions: string[]; mode: "and" | "or" | "startWith" | "endWith" | "exact" | "regex"; } ``` ### Utility Functions ```ts // Configure permissions configurePermission(permissions: string[] | Ref<string[]>, options?: { developmentMode?: boolean }) // Check permissions programmatically hasPermission(permission: PermissionValue, userPermissions?: string[]): Promise<boolean> // Initialize from storage initPermissionDirectiveIfNeeded(): void // Clear cache clearPermissionCache(): void // Get current permissions getCurrentPermissions(): string[] ``` --- ## šŸ” Advanced Examples ### Dynamic Role-Based Menu ```vue <template> <nav class="sidebar"> <router-link v-permission="'dashboard.view'" to="/dashboard" class="nav-item" > Dashboard </router-link> <router-link v-permission="['users.view', 'admin.users']" to="/users" class="nav-item" > Users </router-link> <div v-permission="{ permissions: ['^admin\\.'], mode: 'regex' }" class="admin-section" > <h3>Admin</h3> <router-link v-permission="'admin.settings'" to="/admin/settings" >Settings</router-link > <router-link v-permission="'admin.reports'" to="/admin/reports" >Reports</router-link > </div> </nav> </template> ``` ### Conditional Form Fields ```vue <template> <form @submit="handleSubmit"> <input v-model="user.name" placeholder="Name" /> <input v-model="user.email" placeholder="Email" /> <!-- Only admins can change roles --> <select v-permission:show="'admin.users'" v-model="user.role"> <option value="user">User</option> <option value="admin">Admin</option> </select> <!-- Different submit buttons based on permissions --> <button v-permission="'user.create'" type="submit">Create User</button> <button v-permission="{ permissions: ['user.edit', 'admin.users'], mode: 'and' }" type="submit" > Advanced Save </button> </form> </template> ``` --- ## šŸ› Debugging ### Development Mode ```ts app.use(PermissionPlugin, { permissions: userPermissions, developmentMode: true, // Enables console warnings and logs }); ``` ### Common Issues 1. **Permissions not updating**: Clear cache after permission changes ```ts clearPermissionCache(); configurePermission(newPermissions); ``` 2. **Route guard not working**: Ensure `getAuthState` returns correct values ```ts getAuthState: () => ({ isAuthenticated: !!localStorage.getItem("token"), user: JSON.parse(localStorage.getItem("user") || "{}"), }); ``` 3. **Directive not reactive**: Use `Ref<string[]>` for reactive permissions ```ts const permissions = ref(["user.view"]); configurePermissionDirective(permissions); ``` --- ## šŸ“‚ Project Structure ``` vue-nuxt-permission/ ā”œā”€ā”€ src/ │ ā”œā”€ā”€ directives/ │ │ └── v-permission.ts # Directive implementation │ ā”œā”€ā”€ guards/ │ │ └── globalGuard.ts # Route guard logic │ ā”œā”€ā”€ utils/ │ │ ā”œā”€ā”€ permissionCache.ts # Caching system │ │ ā”œā”€ā”€ permissionStorage.ts # LocalStorage Base64 persistence │ │ └── permissionHelpers.ts # Core permission logic │ ā”œā”€ā”€ plugin.ts # Vue plugin registration │ ā”œā”€ā”€ index.ts # Main exports │ └── types.ts # TypeScript definitions ā”œā”€ā”€ package.json └── README.md ``` --- ## šŸ¤ Contributing 1. Fork the repository 2. Create your feature branch (`git checkout -b feature/amazing-feature`) 3. Commit your changes (`git commit -m 'Add amazing feature'`) 4. Push to the branch (`git push origin feature/amazing-feature`) 5. Open a Pull Request --- ## šŸ“„ License MIT Ā© kerolos --- ## šŸ†• Changelog ### v2.0.0 - Rename `configurePermissionDirective` → `configurePermission` - Add `.lazy` and `.once` directive modifiers - Optional `userPermissions` param in `hasPermission` - Improved caching and Base64 storage wording - Updated docs and examples