diff --git a/shell/middleware/authenticated.js b/shell/middleware/authenticated.js index 3fcc463ac98..698e55ec7fd 100644 --- a/shell/middleware/authenticated.js +++ b/shell/middleware/authenticated.js @@ -1,86 +1,21 @@ -import { NAME as EXPLORER } from '@shell/config/product/explorer'; -import { SETUP, TIMED_OUT } from '@shell/config/query-params'; +import { SETUP } from '@shell/config/query-params'; import { SETTING } from '@shell/config/settings'; import { MANAGEMENT, NORMAN, DEFAULT_WORKSPACE } from '@shell/config/types'; import { applyProducts } from '@shell/store/type-map'; -import { findBy } from '@shell/utils/array'; import { ClusterNotFoundError, RedirectToError } from '@shell/utils/error'; import { get } from '@shell/utils/object'; 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 { canViewResource } from '@shell/utils/auth'; -import { getClusterFromRoute, getProductFromRoute, getPackageFromRoute, getResourceFromRoute } from '@shell/utils/router'; +import { + validateResource, setProduct, isLoggedIn, notLoggedIn, noAuth, tryInitialSetup, findMe +} from '@shell/utils/auth'; +import { getClusterFromRoute, getProductFromRoute, getPackageFromRoute } from '@shell/utils/router'; import { fetchInitialSettings } from '@shell/utils/settings'; let beforeEachSetup = false; -function setProduct(store, to) { - let product = getProductFromRoute(to); - - // since all products are hardcoded as routes (ex: c-local-explorer), if we match the wildcard route it means that the product does not exist - if ((product && (!to.matched.length || (to.matched.length && to.matched[0].path === '/c/:cluster/:product'))) || - // if the product grabbed from the route is not registered, then we don't have it! - (product && !store.getters['type-map/isProductRegistered'](product))) { - const error = new Error(store.getters['i18n/t']('nav.failWhale.productNotFound', { productNotFound: product }, true)); - - store.dispatch('loadingError', error); - - throw new Error('loadingError', new Error(store.getters['i18n/t']('nav.failWhale.productNotFound', { productNotFound: product }, true))); - } - - if ( !product ) { - product = EXPLORER; - } - - const oldProduct = store.getters['productId']; - const oldStore = store.getters['currentProduct']?.inStore; - - if ( product !== oldProduct ) { - store.commit('setProduct', product); - } - - const neuStore = store.getters['currentProduct']?.inStore; - - if ( neuStore !== oldStore ) { - // If the product store changes, clear the catalog. - // There might be management catalog items in it vs cluster. - store.commit('catalog/reset'); - } -} - -/** - * Check that the resource is valid, if not redirect to fail whale - * - * This requires that - * - product is set - * - product's store is set and setup (so we can check schema's within it) - * - product's store has the schemaFor getter (extension stores might not have it) - * - there's a resource associated with route (meta or param) - */ -function validateResource(store, to) { - const product = store.getters['currentProduct']; - const resource = getResourceFromRoute(to); - - // In order to check a resource is valid we need these - if (!product || !resource) { - return false; - } - - if (canViewResource(store, resource)) { - return false; - } - - // Unknown resource, redirect to fail whale - - const error = new Error(store.getters['i18n/t']('nav.failWhale.resourceNotFound', { resource }, true)); - - store.dispatch('loadingError', error); - - throw error; -} - export default async function({ route, store, redirect, from, $plugin, next }) { @@ -141,26 +76,6 @@ export default async function({ } } - // Make sure you're actually logged in - function isLoggedIn(me) { - store.commit('auth/hasAuth', true); - store.commit('auth/loggedInAs', me.id); - } - - function notLoggedIn() { - store.commit('auth/hasAuth', true); - - if ( route.name === 'index' ) { - return redirect(302, '/auth/login'); - } else { - return redirect(302, `/auth/login?${ TIMED_OUT }`); - } - } - - function noAuth() { - store.commit('auth/hasAuth', false); - } - 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'); @@ -175,13 +90,13 @@ export default async function({ const fromHeader = store.getters['auth/fromHeader']; if ( fromHeader === 'none' ) { - noAuth(); + noAuth(store); } else if ( fromHeader === 'true' ) { const me = await findMe(store); - isLoggedIn(me); + isLoggedIn(store, me); } else if ( fromHeader === 'false' ) { - notLoggedIn(); + notLoggedIn(store, redirect, route); return; } else { @@ -189,15 +104,15 @@ export default async function({ try { const me = await findMe(store); - isLoggedIn(me); + isLoggedIn(store, me); } catch (e) { const status = e?._status; if ( status === 404 ) { - noAuth(); + noAuth(store); } else { if ( status === 401 ) { - notLoggedIn(); + notLoggedIn(store, redirect, route); } else { store.commit('setError', { error: e, locationError: new Error('Auth Middleware') }); } @@ -380,36 +295,3 @@ export default async function({ } } } - -async function findMe(store) { - // First thing we do in loadManagement is fetch principals anyway.... so don't ?me=true here - const principals = await store.dispatch('rancher/findAll', { - type: NORMAN.PRINCIPAL, - opt: { - url: '/v3/principals', - redirectUnauthorized: false, - } - }); - - const me = findBy(principals, 'me', true); - - return me; -} - -async function tryInitialSetup(store, password = 'admin') { - try { - const res = await store.dispatch('auth/login', { - provider: 'local', - body: { - username: 'admin', - password - }, - }); - - return res._status === 200; - } catch (e) { - console.error('Error trying initial setup', e); // eslint-disable-line no-console - - return false; - } -} diff --git a/shell/utils/auth.js b/shell/utils/auth.js index 6b09ca1acf6..ae134c441c3 100644 --- a/shell/utils/auth.js +++ b/shell/utils/auth.js @@ -1,8 +1,13 @@ import { Popup, popupWindowOptions } from '@shell/utils/window'; import { parse as parseUrl, addParam } from '@shell/utils/url'; -import { BACK_TO, SPA, _EDIT, _FLAGGED } from '@shell/config/query-params'; -import { MANAGEMENT } from '@shell/config/types'; +import { + BACK_TO, SPA, _EDIT, _FLAGGED, TIMED_OUT +} from '@shell/config/query-params'; +import { MANAGEMENT, NORMAN } from '@shell/config/types'; import { allHash } from '@shell/utils/promise'; +import { getProductFromRoute, getResourceFromRoute } from '@shell/utils/router'; +import { NAME as EXPLORER } from '@shell/config/product/explorer'; +import { findBy } from '@shell/utils/array'; export function openAuthPopup(url, provider) { const popup = new Popup(() => { @@ -161,3 +166,146 @@ export const canViewResource = (store, resource) => { return !!validResource; }; + +// ************************************************************ +// +// BELOW ARE METHODS THAT ARE A PART OF THE AUTHENTICATED MIDDLEWARE REMOVAL. THIS IS A TEMPORARY HOME FOR THESE UTILS AND SHOULD BE REWRITTEN, MOVED OR DELETED. +// +// TODO: Remove and refactor everything below for more clarity and better organization. +// +// ************************************************************ + +/** + * Attempt to set the product in our datastore if the route matches a known product. Otherwise show an error page instead. + */ +export function setProduct(store, to) { + let product = getProductFromRoute(to); + + // since all products are hardcoded as routes (ex: c-local-explorer), if we match the wildcard route it means that the product does not exist + if ((product && (!to.matched.length || (to.matched.length && to.matched[0].path === '/c/:cluster/:product'))) || + // if the product grabbed from the route is not registered, then we don't have it! + (product && !store.getters['type-map/isProductRegistered'](product))) { + const error = new Error(store.getters['i18n/t']('nav.failWhale.productNotFound', { productNotFound: product }, true)); + + store.dispatch('loadingError', error); + + throw new Error('loadingError', new Error(store.getters['i18n/t']('nav.failWhale.productNotFound', { productNotFound: product }, true))); + } + + if ( !product ) { + product = EXPLORER; + } + + const oldProduct = store.getters['productId']; + const oldStore = store.getters['currentProduct']?.inStore; + + if ( product !== oldProduct ) { + store.commit('setProduct', product); + } + + const neuStore = store.getters['currentProduct']?.inStore; + + if ( neuStore !== oldStore ) { + // If the product store changes, clear the catalog. + // There might be management catalog items in it vs cluster. + store.commit('catalog/reset'); + } +} + +/** + * Check that the resource is valid, if not redirect to fail whale + * + * This requires that + * - product is set + * - product's store is set and setup (so we can check schema's within it) + * - product's store has the schemaFor getter (extension stores might not have it) + * - there's a resource associated with route (meta or param) + */ +export function validateResource(store, to) { + const product = store.getters['currentProduct']; + const resource = getResourceFromRoute(to); + + // In order to check a resource is valid we need these + if (!product || !resource) { + return false; + } + + if (canViewResource(store, resource)) { + return false; + } + + // Unknown resource, redirect to fail whale + + const error = new Error(store.getters['i18n/t']('nav.failWhale.resourceNotFound', { resource }, true)); + + store.dispatch('loadingError', error); + + throw error; +} + +/** + * Attempt to load the current user's principal + */ +export async function findMe(store) { + // First thing we do in loadManagement is fetch principals anyway.... so don't ?me=true here + const principals = await store.dispatch('rancher/findAll', { + type: NORMAN.PRINCIPAL, + opt: { + url: '/v3/principals', + redirectUnauthorized: false, + } + }); + + const me = findBy(principals, 'me', true); + + return me; +} + +/** + * Attempt to login with default credentials. Note: I think that this may actually be outdated since we don't use these default credentials anymore on setup. + */ +export async function tryInitialSetup(store, password = 'admin') { + try { + const res = await store.dispatch('auth/login', { + provider: 'local', + body: { + username: 'admin', + password + }, + }); + + return res._status === 200; + } catch (e) { + console.error('Error trying initial setup', e); // eslint-disable-line no-console + + return false; + } +} + +/** + * Record in our state management that we're indeed logged in + */ +export function isLoggedIn(store, me) { + store.commit('auth/hasAuth', true); + store.commit('auth/loggedInAs', me.id); +} + +/** + * Record in our state management that we're not logged in and then redirect to the login page + */ +export function notLoggedIn(store, redirect, route) { + store.commit('auth/hasAuth', true); + + if ( route.name === 'index' ) { + return redirect(302, '/auth/login'); + } else { + return redirect(302, `/auth/login?${ TIMED_OUT }`); + } +} + +/** + * Record in our state management that we don't have any auth providers + */ +export function noAuth(store) { + store.commit('auth/hasAuth', false); +}