mirror of https://github.com/rancher/dashboard.git
Merge pull request #11165 from codyrancher/nav-guards
Pulling out authentication code into router navigation guards
This commit is contained in:
commit
78f10c1c9f
|
|
@ -6,7 +6,7 @@ import BaseResourceList from '@/cypress/e2e/po/lists/base-resource-list.po';
|
|||
*/
|
||||
export default class MgmtUsersListPo extends BaseResourceList {
|
||||
create() {
|
||||
return this.masthead().actions().eq(0).click();
|
||||
return cy.get('[data-testid="masthead-create"]').click();
|
||||
}
|
||||
|
||||
refreshGroupMembership(): AsyncButtonPo {
|
||||
|
|
|
|||
|
|
@ -11,10 +11,11 @@ const usersPo = new UsersPo('_');
|
|||
const userCreate = usersPo.createEdit();
|
||||
const sideNav = new ProductNavPo();
|
||||
|
||||
const runTimestamp = +new Date();
|
||||
const runPrefix = `e2e-test-${ runTimestamp }`;
|
||||
const downloadsFolder = Cypress.config('downloadsFolder');
|
||||
const globalRoleName = `${ runPrefix }-my-global-role`;
|
||||
|
||||
let runTimestamp;
|
||||
let runPrefix;
|
||||
let globalRoleName;
|
||||
|
||||
describe('Roles', { tags: ['@usersAndAuths', '@adminUser'] }, () => {
|
||||
beforeEach(() => {
|
||||
|
|
@ -23,6 +24,12 @@ describe('Roles', { tags: ['@usersAndAuths', '@adminUser'] }, () => {
|
|||
});
|
||||
|
||||
it('can create a Global Role', () => {
|
||||
// We want to define these here because if this test fails after it created the global role all subsequent
|
||||
// retries will reference the wrong global-role because a second roll will with the same name but different id will be created
|
||||
runTimestamp = +new Date();
|
||||
runPrefix = `e2e-test-${ runTimestamp }`;
|
||||
globalRoleName = `${ runPrefix }-my-global-role`;
|
||||
|
||||
// create global role
|
||||
const fragment = 'GLOBAL';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import Vue from 'vue';
|
||||
import Router from 'vue-router';
|
||||
import Routes from '@shell/config/router/routes';
|
||||
import { installNavigationGuards } from '@shell/config/router/navigation-guards';
|
||||
|
||||
Vue.use(Router);
|
||||
|
||||
|
|
@ -12,9 +13,11 @@ export const routerOptions = {
|
|||
fallback: false
|
||||
};
|
||||
|
||||
export function extendRouter(config) {
|
||||
export function extendRouter(config, context) {
|
||||
const base = (config._app && config._app.basePath) || routerOptions.base;
|
||||
const router = new Router({ ...routerOptions, base });
|
||||
|
||||
installNavigationGuards(router, context);
|
||||
|
||||
return router;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
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';
|
||||
|
||||
export function install(router, context) {
|
||||
router.beforeEach((from, to, next) => attemptFirstLogin(from, to, next, context));
|
||||
}
|
||||
|
||||
export async function attemptFirstLogin(from, to, next, { store }) {
|
||||
if (routeNameMatched(to, 'unauthenticated')) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// Initial ?setup=admin-password can technically be on any route
|
||||
let initialPass = to.query[SETUP];
|
||||
let firstLogin = null;
|
||||
|
||||
try {
|
||||
const res = store.getters['management/byId'](MANAGEMENT.SETTING, SETTING.FIRST_LOGIN);
|
||||
const plSetting = store.getters['management/byId'](MANAGEMENT.SETTING, SETTING.PL);
|
||||
|
||||
firstLogin = res?.value === 'true';
|
||||
|
||||
if (!initialPass && plSetting?.value === 'Harvester') {
|
||||
initialPass = 'admin';
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
if ( firstLogin === null ) {
|
||||
try {
|
||||
const res = await store.dispatch('rancher/find', {
|
||||
type: NORMAN.SETTING,
|
||||
id: SETTING.FIRST_LOGIN,
|
||||
opt: { url: `/v3/settings/${ SETTING.FIRST_LOGIN }` }
|
||||
});
|
||||
|
||||
firstLogin = res?.value === 'true';
|
||||
|
||||
const plSetting = await store.dispatch('rancher/find', {
|
||||
type: NORMAN.SETTING,
|
||||
id: SETTING.PL,
|
||||
opt: { url: `/v3/settings/${ SETTING.PL }` }
|
||||
});
|
||||
|
||||
if (!initialPass && plSetting?.value === 'Harvester') {
|
||||
initialPass = 'admin';
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO show error if firstLogin and default pass doesn't work
|
||||
if ( firstLogin ) {
|
||||
const ok = await tryInitialSetup(store, initialPass);
|
||||
|
||||
if (ok) {
|
||||
if (initialPass) {
|
||||
store.dispatch('auth/setInitialPass', initialPass);
|
||||
}
|
||||
|
||||
return next({ name: 'auth-setup' });
|
||||
} else {
|
||||
return next({ name: 'auth-login' });
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
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';
|
||||
|
||||
/**
|
||||
* Install our router navigation guards. i.e. router.beforeEach(), router.afterEach()
|
||||
*/
|
||||
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];
|
||||
|
||||
navigationGuardInstallers.forEach((installer) => installer(router, context));
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import { fetchInitialSettings } from '@shell/utils/settings';
|
||||
|
||||
export function install(router, context) {
|
||||
router.beforeEach((from, to, next) => loadInitialSettings(from, to, next));
|
||||
}
|
||||
|
||||
export async function loadInitialSettings(from, to, next) {
|
||||
try {
|
||||
await fetchInitialSettings();
|
||||
} catch (ex) {}
|
||||
|
||||
next();
|
||||
}
|
||||
|
|
@ -15,8 +15,8 @@ import { installInjectedPlugins } from 'initialize/install-plugins.js';
|
|||
*/
|
||||
async function extendApp(vueApp) {
|
||||
const config = { rancherEnv: process.env.rancherEnv, dashboardVersion: process.env.version };
|
||||
const router = extendRouter(config);
|
||||
const store = extendStore();
|
||||
const router = extendRouter(config, { store });
|
||||
|
||||
// Add this.$router into store actions/mutations
|
||||
store.$router = router;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
import { SETUP } from '@shell/config/query-params';
|
||||
import { SETTING } from '@shell/config/settings';
|
||||
import { MANAGEMENT, NORMAN, DEFAULT_WORKSPACE } from '@shell/config/types';
|
||||
import { DEFAULT_WORKSPACE } from '@shell/config/types';
|
||||
import { applyProducts } from '@shell/store/type-map';
|
||||
import { ClusterNotFoundError, RedirectToError } from '@shell/utils/error';
|
||||
import { get } from '@shell/utils/object';
|
||||
|
|
@ -9,73 +7,15 @@ 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, tryInitialSetup, findMe
|
||||
validateResource, setProduct, isLoggedIn, notLoggedIn, noAuth, findMe
|
||||
} from '@shell/utils/auth';
|
||||
import { getClusterFromRoute, getProductFromRoute, getPackageFromRoute } from '@shell/utils/router';
|
||||
import { fetchInitialSettings } from '@shell/utils/settings';
|
||||
|
||||
let beforeEachSetup = false;
|
||||
|
||||
export default async function({
|
||||
route, store, redirect, from, $plugin, next
|
||||
}) {
|
||||
// Initial ?setup=admin-password can technically be on any route
|
||||
let initialPass = route.query[SETUP];
|
||||
let firstLogin = null;
|
||||
|
||||
try {
|
||||
// Load settings, which will either be just the public ones if not logged in, or all if you are
|
||||
await fetchInitialSettings(store);
|
||||
|
||||
const res = store.getters['management/byId'](MANAGEMENT.SETTING, SETTING.FIRST_LOGIN);
|
||||
const plSetting = store.getters['management/byId'](MANAGEMENT.SETTING, SETTING.PL);
|
||||
|
||||
firstLogin = res?.value === 'true';
|
||||
|
||||
if (!initialPass && plSetting?.value === 'Harvester') {
|
||||
initialPass = 'admin';
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
if ( firstLogin === null ) {
|
||||
try {
|
||||
const res = await store.dispatch('rancher/find', {
|
||||
type: NORMAN.SETTING,
|
||||
id: SETTING.FIRST_LOGIN,
|
||||
opt: { url: `/v3/settings/${ SETTING.FIRST_LOGIN }` }
|
||||
});
|
||||
|
||||
firstLogin = res?.value === 'true';
|
||||
|
||||
const plSetting = await store.dispatch('rancher/find', {
|
||||
type: NORMAN.SETTING,
|
||||
id: SETTING.PL,
|
||||
opt: { url: `/v3/settings/${ SETTING.PL }` }
|
||||
});
|
||||
|
||||
if (!initialPass && plSetting?.value === 'Harvester') {
|
||||
initialPass = 'admin';
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO show error if firstLogin and default pass doesn't work
|
||||
if ( firstLogin ) {
|
||||
const ok = await tryInitialSetup(store, initialPass);
|
||||
|
||||
if (ok) {
|
||||
if (initialPass) {
|
||||
store.dispatch('auth/setInitialPass', initialPass);
|
||||
}
|
||||
|
||||
return redirect({ name: 'auth-setup' });
|
||||
} else {
|
||||
return redirect({ name: 'auth-login' });
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
|
|
|
|||
|
|
@ -75,6 +75,30 @@ export const getResourceFromRoute = (to) => {
|
|||
return resource;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a route it will look through the matching parent routes to see if any match the fn (predicate) criteria
|
||||
*
|
||||
* @param {*} to a VueRouter Route object
|
||||
* @param {*} fn fn is a predicate which is passed a matched route. It will return true to indicate there was a matching route and false otherwise
|
||||
* @returns true if a matching route was found, false otherwise
|
||||
*/
|
||||
export const routeMatched = (to, fn) => {
|
||||
const matched = to?.matched || [];
|
||||
|
||||
return !!matched.find(fn);
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a route and a name it will look through the matching parent routes to see if any have the specified name
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
export const routeNameMatched = (to, routeName) => {
|
||||
return routeMatched(to, (matched) => (matched?.name === routeName));
|
||||
};
|
||||
|
||||
function findMeta(route, key) {
|
||||
if (route?.meta) {
|
||||
const meta = Array.isArray(route.meta) ? route.meta : [route.meta];
|
||||
|
|
|
|||
Loading…
Reference in New Issue