From b81a1f852238e164c36b812e586f9d25dab2dd00 Mon Sep 17 00:00:00 2001 From: Cody Jackson Date: Wed, 5 Jun 2024 14:53:41 -0700 Subject: [PATCH] Extracted the authentication portion of the authenticated middleware into a navigation guard --- .../navigation-guards/attempt-first-login.js | 4 +- .../navigation-guards/authentication.js | 63 +++++++++++++++++++ .../config/router/navigation-guards/index.js | 3 +- shell/config/router/routes.js | 5 ++ shell/initialize/entry-helpers.js | 4 +- shell/middleware/authenticated.js | 51 +-------------- shell/utils/auth.js | 4 +- shell/utils/router.js | 11 ++-- 8 files changed, 84 insertions(+), 61 deletions(-) create mode 100644 shell/config/router/navigation-guards/authentication.js diff --git a/shell/config/router/navigation-guards/attempt-first-login.js b/shell/config/router/navigation-guards/attempt-first-login.js index d41bd606b31..4ee3f6b7ae7 100644 --- a/shell/config/router/navigation-guards/attempt-first-login.js +++ b/shell/config/router/navigation-guards/attempt-first-login.js @@ -2,14 +2,14 @@ import { SETUP } from '@shell/config/query-params'; import { SETTING } from '@shell/config/settings'; import { MANAGEMENT, NORMAN } from '@shell/config/types'; import { tryInitialSetup } from '@shell/utils/auth'; -import { routeNameMatched } from '@shell/utils/router'; +import { routeRequiresAuthentication } from '@shell/utils/router'; export function install(router, context) { router.beforeEach((to, from, next) => attemptFirstLogin(to, from, next, context)); } export async function attemptFirstLogin(to, from, next, { store }) { - if (routeNameMatched(to, 'unauthenticated')) { + if (!routeRequiresAuthentication(to)) { return next(); } diff --git a/shell/config/router/navigation-guards/authentication.js b/shell/config/router/navigation-guards/authentication.js new file mode 100644 index 00000000000..91f3bb7bad9 --- /dev/null +++ b/shell/config/router/navigation-guards/authentication.js @@ -0,0 +1,63 @@ +import { routeRequiresAuthentication } from '@shell/utils/router'; +import { isLoggedIn, notLoggedIn, noAuth, findMe } from '@shell/utils/auth'; + +export function install(router, context) { + router.beforeEach((to, from, next) => authenticate(to, from, next, context)); +} + +export async function authenticate(to, from, next, { store }) { + if (!routeRequiresAuthentication(to)) { + return next(); + } + + if ( store.getters['auth/enabled'] !== false && !store.getters['auth/loggedIn'] ) { + // `await` so we have one successfully request whilst possibly logged in (ensures fromHeader is populated from `x-api-cattle-auth`) + await store.dispatch('auth/getUser'); + + const v3User = store.getters['auth/v3User'] || {}; + + if (v3User?.mustChangePassword) { + return next({ name: 'auth-setup' }); + } + + // In newer versions the API calls return the auth state instead of having to make a new call all the time. + const fromHeader = store.getters['auth/fromHeader']; + + if ( fromHeader === 'none' ) { + noAuth(store); + } else if ( fromHeader === 'true' ) { + const me = await findMe(store); + + isLoggedIn(store, me); + } else if ( fromHeader === 'false' ) { + notLoggedIn(store, next, to); + + return; + } else { + // Older versions look at principals and see what happens + try { + const me = await findMe(store); + + isLoggedIn(store, me); + } catch (e) { + const status = e?._status; + + if ( status === 404 ) { + noAuth(store); + } else { + if ( status === 401 ) { + notLoggedIn(store, next, to); + } else { + store.commit('setError', { error: e, locationError: new Error('Auth Middleware') }); + } + + return; + } + } + } + + store.dispatch('gcStartIntervals'); + } + + next(); +} diff --git a/shell/config/router/navigation-guards/index.js b/shell/config/router/navigation-guards/index.js index 71bcde3c8da..6cf2a646519 100644 --- a/shell/config/router/navigation-guards/index.js +++ b/shell/config/router/navigation-guards/index.js @@ -1,5 +1,6 @@ import { install as installLoadInitialSettings } from '@shell/config/router/navigation-guards/load-initial-settings'; import { install as installAttemptFirstLogin } from '@shell/config/router/navigation-guards/attempt-first-login'; +import { install as installAuthentication } from '@shell/config/router/navigation-guards/authentication'; /** * Install our router navigation guards. i.e. router.beforeEach(), router.afterEach() @@ -8,7 +9,7 @@ export function installNavigationGuards(router, context) { // NOTE: the order of the installation matters. // Be intentional when adding, removing or modifying the guards that are installed. - const navigationGuardInstallers = [installLoadInitialSettings, installAttemptFirstLogin]; + const navigationGuardInstallers = [installLoadInitialSettings, installAttemptFirstLogin, installAuthentication]; navigationGuardInstallers.forEach((installer) => installer(router, context)); } diff --git a/shell/config/router/routes.js b/shell/config/router/routes.js index 7744fb85edf..ba7edaf43cd 100644 --- a/shell/config/router/routes.js +++ b/shell/config/router/routes.js @@ -10,6 +10,7 @@ export default [ { path: '/', component: () => interopDefault(import('@shell/components/templates/default.vue')), + meta: { requiresAuthentication: true }, children: [ { path: '', @@ -27,12 +28,14 @@ export default [ path: '', component: () => interopDefault(import('@shell/components/templates/blank.vue')), name: 'blank', + meta: { requiresAuthentication: true }, children: [ ] }, { path: '', component: () => interopDefault(import('@shell/components/templates/home.vue')), + meta: { requiresAuthentication: true }, children: [ { path: '/home', @@ -50,6 +53,7 @@ export default [ path: '', component: () => interopDefault(import('@shell/components/templates/plain.vue')), name: 'plain', + meta: { requiresAuthentication: true }, children: [ { path: '/about', @@ -145,6 +149,7 @@ export default [ path: '', component: () => interopDefault(import('@shell/components/templates/default.vue')), name: 'default', + meta: { requiresAuthentication: true }, children: [ { path: '/clusters', diff --git a/shell/initialize/entry-helpers.js b/shell/initialize/entry-helpers.js index e112be114c6..5b781c3607f 100644 --- a/shell/initialize/entry-helpers.js +++ b/shell/initialize/entry-helpers.js @@ -265,7 +265,9 @@ export async function mountApp(appPartials, VueClass) { // Add beforeEach router hooks router.beforeEach(render.bind(vueApp)); router.afterEach((from, to) => { - updatePageTitle(getVendor()); + if (from?.name !== to?.name) { + updatePageTitle(getVendor()); + } }); // First render on client-side diff --git a/shell/middleware/authenticated.js b/shell/middleware/authenticated.js index 7696fa8cd8e..987e99d7e6c 100644 --- a/shell/middleware/authenticated.js +++ b/shell/middleware/authenticated.js @@ -6,9 +6,7 @@ import dynamicPluginLoader from '@shell/pkg/dynamic-plugin-loader'; import { AFTER_LOGIN_ROUTE, WORKSPACE } from '@shell/store/prefs'; import { BACK_TO } from '@shell/config/local-storage'; import { NAME as FLEET_NAME } from '@shell/config/product/fleet.js'; -import { - validateResource, setProduct, isLoggedIn, notLoggedIn, noAuth, findMe -} from '@shell/utils/auth'; +import { validateResource, setProduct } from '@shell/utils/auth'; import { getClusterFromRoute, getProductFromRoute, getPackageFromRoute } from '@shell/utils/router'; let beforeEachSetup = false; @@ -17,52 +15,7 @@ export default async function({ route, store, redirect, from, $plugin, next }) { if ( store.getters['auth/enabled'] !== false && !store.getters['auth/loggedIn'] ) { - // `await` so we have one successfully request whilst possibly logged in (ensures fromHeader is populated from `x-api-cattle-auth`) - await store.dispatch('auth/getUser'); - - const v3User = store.getters['auth/v3User'] || {}; - - if (v3User?.mustChangePassword) { - return redirect({ name: 'auth-setup' }); - } - - // In newer versions the API calls return the auth state instead of having to make a new call all the time. - const fromHeader = store.getters['auth/fromHeader']; - - if ( fromHeader === 'none' ) { - noAuth(store); - } else if ( fromHeader === 'true' ) { - const me = await findMe(store); - - isLoggedIn(store, me); - } else if ( fromHeader === 'false' ) { - notLoggedIn(store, redirect, route); - - return; - } else { - // Older versions look at principals and see what happens - try { - const me = await findMe(store); - - isLoggedIn(store, me); - } catch (e) { - const status = e?._status; - - if ( status === 404 ) { - noAuth(store); - } else { - if ( status === 401 ) { - notLoggedIn(store, redirect, route); - } else { - store.commit('setError', { error: e, locationError: new Error('Auth Middleware') }); - } - - return; - } - } - } - - store.dispatch('gcStartIntervals'); + return; } const backTo = window.localStorage.getItem(BACK_TO); diff --git a/shell/utils/auth.js b/shell/utils/auth.js index e515731ca21..96c59397a7d 100644 --- a/shell/utils/auth.js +++ b/shell/utils/auth.js @@ -297,9 +297,9 @@ export function notLoggedIn(store, redirect, route) { store.commit('auth/hasAuth', true); if ( route.name === 'index' ) { - return redirect(302, '/auth/login'); + return redirect('/auth/login'); } else { - return redirect(302, `/auth/login?${ TIMED_OUT }`); + return redirect(`/auth/login?${ TIMED_OUT }`); } } diff --git a/shell/utils/router.js b/shell/utils/router.js index 72c0a8ee0de..c945d5aca8b 100644 --- a/shell/utils/router.js +++ b/shell/utils/router.js @@ -89,14 +89,13 @@ export const routeMatched = (to, fn) => { }; /** - * Given a route and a name it will look through the matching parent routes to see if any have the specified name - * + * Checks to see if the route requires authentication by taking a look at the route and it's parents 'meta' to see if it + * contains { requiresAuthentication: true } * @param {*} to a VueRouter Route object - * @param {*} routeName the name of a route you're checking to see if it was matched. - * @returns true if a matching route was found, false otherwise + * @returns true if the route requires authentication, false otherwise */ -export const routeNameMatched = (to, routeName) => { - return routeMatched(to, (matched) => (matched?.name === routeName)); +export const routeRequiresAuthentication = (to) => { + return routeMatched(to, (matched) => matched.meta?.requiresAuthentication); }; function findMeta(route, key) {