2.1.1 • Published 4 years ago

v-access v2.1.1

Weekly downloads
5
License
MIT
Repository
github
Last release
4 years ago

An authentication solution based on Vue.js v2.x, including elements-based control and route-based control.

DependenciesRequired
vue✔️
vue-router✔️

Features

  • Minimal design: Only one ability/privilege list and give you all element-based and route-based authentications.

  • Smooth changes: Support any dynamic private routes addition and deletion without page reloading.

Installation

# using npm
npm i v-access

# using yarn
yarn add v-access

Prerequisites

  • Ability type

    type Ability = string

    Ability should be a global unique identifier and string type.

  • Routes type

    interface RouteWithAbility extends RouteConfig {
      readonly children?: RouteWithAbility[]
      readonly meta?: {
        strict?: Ability[]
        weak?: Ability[]
        ability?: Ability
        [key: string]: any
      }
    }

    More details could be found from here.

Best practice

NOTICE: This section is only the best practice recommendation, not required.

The entire authorization system is based on an ability/privilege list provided by any back-end services. Every element in the list represents an ability that is used to access the corresponding database. A user role consists of multiple abilities, represents an ability set. One actual user could have multiple user roles.

There is a best practice that uses syntax like [scope].[module].[ability] (eg. IAM) to represents one ability. In this case, [scope] is optional if you have no other external systems (scope).

The advantage of this design is that the role of multiple users or the ability of multiple roles can be intersected. Multiple abilities can be arbitrarily combined to form a flexible abilities set.

The following chart represents an actual user's ability set:

                      +--> github.repo.read
      +-> user role 1 |
      |               +--> npm.org.import
      |
      |               +--> github.pull.read
user -+-> user role 2 |
      |               +--> npm.downloads.read
      |
      |               +--> github.action.read
      +-> user role 3 |
                      +--> npm.packages.publish

No matter what your ability name is, you should always call init function with a full ability list first.

Initialization

import Vue from 'vue'
import VAccess from 'v-access'

Vue.use(VAccess)

This package should be installed before the root Vue instance creation. This process will inject a global component named VAccess and a prototype property named $$auth.

import { init } from 'v-access'

export default {
  name: 'AnyComponent',

  // ... omit all unrelated properties

  created() {
    // a vuex action or http request
    fetchAbilities(payload)
      .then(list => list.map(abilityInfo => ability.name)) // ability serialization
      .then(abilities =>
        init({
          vm: this, // or this.$router
          abilities,
          redirect: '/forbidden',
          routes: [
            /* routes which need to add to vue-router would be filtered by abilities first */
          ]
        })
      )
      .catch(console.error)
  }
}

No matter the original abilities structure is, you should always pass an Ability identity list (a string[] type) to init function for initializing global authentication functionality.

interface InitOptions {
  vm: Vue | VueRouter
  abilities: Ability[]
  redirect: string
  routes?: RouteWithAbility[]
}

export declare function init({
  vm,
  abilities,
  redirect,
  routes
}: InitOptions): void

NOTE: redirect only support a fullPath string, not object type.

As you may have noticed, you can pass a global preset private routes collection to init function for dynamic routes addition. All valid private routes generation could be handled by this package and will be filtered by abilities set.

Scenario

This case would be useful when you want to create private routes that need to be filtered by the current user abilities.

How to authenticate ability

  1. Using element-based authentication

    The results of the following two authentication ways are reactive.

    1. VAccess component

      <v-access :ability="['github.repo.read', 'github.repo.pull']">
        <!-- any child components or HTML nodes -->
      </v-access>
      
      <!-- or -->
      <v-access strict :ability="['github.repo.read', 'github.repo.pull']">
        <!-- any child components or HTML nodes -->
      </v-access>
      
      <!-- or -->
      <v-access :ability="github.repo.read">
        <!-- any child components or HTML nodes -->
      </v-access>
      PropsTypeDescription
      abilityAbility or Ability[]An ability or ability set that needs to be authenticated
      strictbooleanWhether we should authenticate every abilities in the list
    2. $$auth object

      The following table describes several $$auth authentication functions.

      FunctionTypeDescription
      has(ability: Ability) => booleanAn ability that needs to be authenticated
      verifyAll(abilities: Ability[]) => booleanWhether we should authenticate every ability in the list
      verifySome(abilities: Ability[]) => booleanWhether we should authenticate at least one ability in the list
  2. Using route-based authentication

    const routes = [
      // This route always pass authentication
      {
        name: 'PublicRoutes',
        path: '/public',
        component: () =>
          import(/* webpackChunkName: 'page-public' */ './views/Public.vue')
      },
      {
        name: 'PrivateRoutes',
        path: '/private',
        component: () =>
          import(/* webpackChunkName: 'page-private' */ './views/Private.vue'),
        meta: {
          strict: ['github.repo.read', 'github.repo.pull']
          // or
          // weak: ['github.repo.read', 'github.repo.pull'],
          // or
          // ability: 'github.repo.read'
        }
      }
    ]
    Meta propObjective
    strictWhether we should authenticate every ability in the list
    weakWhether we should authenticate at least one ability in the list
    abilityA single ability that needs to be authenticated

Reset

export declare function reset(router: VueRouter): void

You should always use reset(theCurrentRouterInstance) to delete all private routes added by init function without any page reloading.

import { reset } from 'v-access'

reset(this.$router)

With other hooks

Separation of concerns is a design principle for separating distinct parts, and implement the high cohesion and low coupling between multiple independent parts. Vue router navigation guard accepts multiple hooks to implement a navigation pipeline via this principle. This is the theoretical basis for v-access implementation. v-access has provided an authorizer as a beforeEach guard.

If you aren't familiar with how multiple global beforeEach hooks work, I strongly recommend you to read the documentation about router.beforeEach.

Changelog

All notable changes to this package will be documented in CHANGELOG file.

License

MIT © Bowen Liu