Routing

Routing #

Role-based routing #

Assume that on server side you already have implemented role-based access to the data. But what about client side? My fetching role during authentication you can set it in Vuex and use it to dynamically change available routes, hide some components or even dynamically render them.

There are some pieces of code to implement it in your web application.

model/role.ts:

export enum Role {
    Admin = 'admin',
    Customer = 'customer',
}

store/routing.ts:

import Vue from 'vue'
import Vuex, {ActionContext, ActionTree, GetterTree, MutationTree} from 'vuex'
import {Role} from '@/model/roles'
import {privateRoutes, resetRouter, staticRoutes} from '@/router'

Vue.use(Vuex)

function hasPermission(route: any, role: Role) {
    if (route.meta && route.meta.roles) {
        if (route.meta.roles.includes('*')) {
            return true
        }
        return route.meta.roles.includes(role)
    }
    return false
}

function filterRoutes(role: Role, routes: any[]): any[] {
    const filteredRoutes: any[] = []

    routes.forEach((route) => {
        const parent = {...route}
        if (hasPermission(route, role)) {
            if (parent.children) {
                parent.children = filterRoutes(role, parent.children)
            }
            filteredRoutes.push(parent)
        }
    })

    return filteredRoutes
}


export class State {
    public routes: any[] = []
    public addRoutes: any[] = []
}

const getters = <GetterTree<State, any>> {
    routes(state: State) {
        return state.routes
    }
}

const mutations = <MutationTree<State>> {
    SET_ROUTES(state: State, routes: any[]) {
        state.addRoutes = routes
        state.routes = staticRoutes.concat(routes)
    },
    RESET_ROUTES(state: State) {
        state.addRoutes = []
        state.routes = []
        resetRouter()
    }
}

const actions = <ActionTree<State, any>> {
    GenerateRoutes(store: ActionContext<State, any>, role: Role) {
        return new Promise((resolve) => {
            const filteredRoutes = filterRoutes(role, privateRoutes)
            store.commit('SET_ROUTES', filteredRoutes)
            resolve(filteredRoutes)
        })
    },
    ResetRoutes(store: ActionContext<State, any>) {
        return new Promise((resolve) => {
            store.commit('RESET_ROUTES')
            resolve()
        })
    }
}

const routing = {
    state: new State(),
    getters,
    mutations,
    actions
}

export default routing

router.ts:

import Vue from 'vue'
import Router from 'vue-router'
import VueRouter from 'vue-router'
import store from '@/store/index'

Vue.use(Router)

export const staticRoutes = [
    // Routes which don't require any authentication.
    // For example Login page
]

export const privateRoutes = [
    // ...
    {path: '/admin', name: 'admin', redirect: '/', meta: {roles: ['admin']}},
    {path: '/accounting', name: 'accounting', redirect: '/', meta: {roles: ['admin', 'customer']}},
    {path: '*', name: 'notFound', redirect: '/', meta: {roles: ['*']}},
    {path: '*', name: 'notFound', redirect: '/', meta: {roles: ['*']}},
    // ...
]


function createRouter() {
    return new VueRouter({
        mode: 'history',
        routes: staticRoutes
    })
}

const router = createRouter()

export const resetRouter = () => {
    const newRouter = createRouter()
    Object.assign((router as any).matcher, (newRouter as any).matcher)
}

router.beforeEach(async (to, from, next) => {
    const isAuthenticated = store.getters.isAuthenticated
    if (isAuthenticated) {
        if (store.getters.routes.length > 0) {
            next()
        } else {
            try {
                const filteredRoutes = await store.dispatch('GenerateRoutes', store.getters.user.role)
                router.addRoutes(filteredRoutes)

                if (to.path === '/login') {
                    next({path: '/', replace: true})
                } else {
                    next({...to, replace: true})
                }
            } catch (error) {
                await store.dispatch('Logout')
                next('/login')
            }
        }
    } else {
        console.log('Not authenticated: go to login')
        if (to.path === '/login') {
            next()
        } else {
            next({
                path: '/login',
                query: {redirect: to.fullPath}
            })
        }
    }
})

export default router

© 2020 Amanbolat Balabekov