Source

router/router-middlewares.js

/**
 * @module router-middlewares
 * @category Router
 */

import Vue from 'vue'
import store from '../store'
import NProgress from 'nprogress'
import { getRouteQuery, getQueryStringValue } from '@/utils/querystring'
import {
  getFirstGrantedRouteFromVueRoutes,
  isVueRouteGranted,
} from '@/services/rights-service.js'

const shouldLog =
  (getQueryStringValue('verbose') || '').includes('1') ||
  (getQueryStringValue('verbose') || '').includes('router')

const isAuthEnabled = true //Disable for tests

NProgress.configure({ showSpinner: false })
var router
function hasAlertMessages() {
  return store.getters['alert/alerts'].length !== 0
}

export function configureMiddlewares(_router) {
  router = _router
  router.beforeEach(beforeEach)
  router.afterEach(afterEach)
}

/**
 * If user cannot access a route (401), we will redirect him to the first granted route
 */
function getFirstGrantedRoute() {
  return getFirstGrantedRouteFromVueRoutes(router.options.routes)
}

/**
 *
 * If login -> if alerts -> proceed to login
 * If login -> if requires auth -> proceed to login
 * If login -> if no alerts/require auth, try autologin
 * If no login -> *require auth
 *
 * *require auth:
 * redirect to the login screen if not logged
 */
async function beforeEach(to, from, next) {
  NProgress.start()
  NProgress.set(0.1)

  /*
  if (from.meta.functionId) {
    Vue.$loggingService.activityAuditManager.stopActivityAudit(
      to.meta.functionId
    );
  }*/

  //Todo: Stop API requests from previous route

  //Do not bring a loder from previous route
  try {
    Vue.$loader.hide()
  } catch (err) {
    console.warn('Loader fail to show') //Might fail if unit-test
  }

  //Wait DOM update (To switch layout)
  Vue.nextTick(() => {
    if (!isAuthEnabled) {
      return next()
    }

    if (to.name === 'login_screen') {
      //If alerts messages present or requires auth, proceed to login screen
      if (hasAlertMessages() || to.params.requiresAuth) {
        return next()
      }

      //On first page load, try auto-login with stored JWT
      return autoLogin(to, from, next)
    } else {
      //Normal routes check for auth...
      return requireAuth(to, from, next)
    }
  })
}

function afterEach(to) {
  setTimeout(() => NProgress.done(), 500)
}

function getTokenFromRoute(route) {
  if (route && route.query && route.query._token) {
    return route.query._token
  } else {
    return getQueryStringValue('_token')
  }
}

function hasEnoughRights(to) {
  if (!(to.meta && to.meta.requiredRights)) {
    return true
  }
  return (
    to.meta.requiredRights
      .map((rightCode) => {
        return Vue.$rights.hasRight(rightCode) ? '1' : '0'
      })
      .join('')
      .indexOf('0') === -1
  )
}

function routeToString(r) {
  return {
    name: r.name,
    query: r.query,
    meta: r.meta,
  }
}

/**
 * Vue router middleware (auth guard)
 * Will redirect to the login screen if not logged
 *
 * @param {*} to
 * @param {*} from
 * @param {*} next
 */
async function requireAuth(to, from, next) {
  shouldLog &&
    Vue.$log.debug('requireAuth', {
      from: routeToString(from),
      to: routeToString(to),
    })

  if (from.name === null) {
    await store.dispatch('auth/syncLogged', {
      token: getTokenFromRoute(to.query._token) || null,
    })
  }
  if (!store.getters['auth/isLogged']) {
    shouldLog &&
      Vue.$log.debug('requireAuth::intercepted', {
        from: routeToString(from),
        to: routeToString(to),
      })
    return router.push({
      name: 'login_screen',
      params: {
        requiresAuth: true,
        redirectAfterAutoLoginRoute: {
          ...to,
        },
      },
    })
  }

  handleNext(to, from, next)
}

async function handleNext(to, from, next) {
  //Case: User has no enough rights to target module
  if (!hasEnoughRights(to)) {
    let existingRoutes = router.options.routes
      .map((r) => ({
        name: r.name,
        meta: r.meta || {},
      }))
      .filter((r) => !['login_screen'].includes(r.name))

    to = {
      hasChange: true,
    }

    //Case: User is redirected to other accessible module
    if (store.getters['auth/rightsList'].length > 0) {
      let firstAccessibleModuleRoute = getFirstGrantedRoute()
      if (firstAccessibleModuleRoute) {
        shouldLog &&
          Vue.$log.debug(
            'not enough rights, redirecting to first granted module:',
            firstAccessibleModuleRoute.name
          )
        to.name = firstAccessibleModuleRoute.name
      }
    }

    //Case: User as no rights to modules
    if (!to.name) {
      shouldLog && Vue.$log.debug('not enough rights, redirecting to login')
      to.name = 'login_screen'
      await store.dispatch('auth/logout')
      store.dispatch(
        'alert/addAlert',
        {
          title: '403',
          text: 'alerts.NOT_ENOUGH_RIGHTS',
          type: 'warning',
        },
        {
          root: true,
        }
      )
    }
  }

  if (to.meta && to.meta.functionId) {
    Vue.$loggingService.activityAuditManager.startActivityAudit(
      to.meta.functionId
    )
  }

  if (to.hasChange) {
    shouldLog && Vue.$log.debug('handling next (has change)')
    return router.push(to)
  }

  if (from.query.s && !to.query.s) {
    if (to.path === from.path) {
      return // This is a no-no via the documentation, but a bug in routing to identical routes strips query params, and this prevents that
    }

    next({
      name: to.name,
      query: {
        s: from.query.s,
      },
    })
  } else {
    next()
  }
}

/**
 *
 * Vue router middleware
 * Will try to autologin using the stored credentials (JWT token)
 *
 * @param {*} to
 * @param {*} from
 * @param {*} next
 */
async function autoLogin(to, from, next) {
  shouldLog && Vue.$log.debug('autoLogin')

  //Case: User is not logged
  if (!store.getters['auth/isLogged']) {
    let nextRoute =
      (to.params.redirectAfterAutoLoginRoute && {
        ...to.params.redirectAfterAutoLoginRoute,
        hasChange: true,
      }) ||
      null
    let nextRouteQuery = getRouteQuery(nextRoute)
    shouldLog &&
      Vue.$log.debug('autoLogin', {
        nextRoute,
        nextRouteQuery,
      })
    await store.dispatch('auth/syncLogged', {
      token: getTokenFromRoute(nextRoute) || null,
    })

    //Case: User autologin success
    if (store.getters['auth/isLogged']) {
      //Case: Try to redirect to next route
      if (nextRoute) {
        shouldLog &&
          console.info(
            `AUTOLOGIN redirecting to next route ${nextRoute.name}`,
            {
              nextRoute,
            }
          )
        return handleNext(nextRoute, from, next)
      } else {
        Vue.$routerPlugin.routeToDefaultRoute()
      }
    }
  } else {
    shouldLog && Vue.$log.debug('autoLogin fails')
  }

  next()
}