Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import routes from '@shell/config/router/routes';
import { handleRouteGuardUnregisteredRoutes } from '@shell/config/router/navigation-guards/route-guard-unregistered-routes';

// Flatten the nested routes array
const flattenRoutes = (records, acc = []) => {
records.forEach((route) => {
acc.push(route);
if (route.children?.length) {
flattenRoutes(route.children, acc);
}
});

return acc;
};

const registeredRoutes = flattenRoutes(routes).filter((r) => !!r.name);

// Build an allTypes map keyed by resource name with matching route names
const allTypes = registeredRoutes.reduce((acc, route) => {
acc[route.name] = { route: { name: route.name } };

return acc;
}, {});

// Store mock that returns the allTypes for any product
const store = { getters: { 'type-map/allTypes': jest.fn(() => ({ all: allTypes })) } };

describe('route-guard-unregistered-routes', () => {
it('does not redirect registered routes', async() => {
for (const route of registeredRoutes) {
const next = jest.fn();

await handleRouteGuardUnregisteredRoutes(
{
name: route.name,
params: { product: 'test-product', resource: route.name },
},
{},
next,
{ store }
);

expect(next).not.toHaveBeenCalledWith(expect.objectContaining({ name: '404' }));
}
});

it('redirects to 404 for an unregistered route/resource with product and resource', async() => {
const next = jest.fn();

await handleRouteGuardUnregisteredRoutes(
{
name: 'not-registered',
params: { product: 'test-product', resource: 'not-registered' },
},
{},
next,
{ store }
);

expect(next).toHaveBeenCalledWith({ name: '404' });
});

it('redirects to 404 for an unregistered route/resource with product', async() => {
const next = jest.fn();

await handleRouteGuardUnregisteredRoutes(
{
name: 'not-registered',
params: { product: 'test-product' },
},
{},
next,
{ store }
);

expect(next).toHaveBeenCalledWith({ name: '404' });
});
});
3 changes: 2 additions & 1 deletion shell/config/router/navigation-guards/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { install as installClusters } from '@shell/config/router/navigation-guar
import { install as installHandleInstallRedirect } from '@shell/config/router/navigation-guards/install-redirect';
import { install as installPageTitle } from '@shell/config/router/navigation-guards/page-title';
import { install as installServerUpgradeGrowl } from '@shell/config/router/navigation-guards/server-upgrade-growl';
import { install as installRouteGuardUnregisteredRoutes } from '@shell/config/router/navigation-guards/route-guard-unregistered-routes';

/**
* Install our router navigation guards. i.e. router.beforeEach(), router.afterEach()
Expand All @@ -17,7 +18,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, installAuthentication, installProducts, installClusters, installRuntimeExtensionRoute, installI18N, installHandleInstallRedirect, installPageTitle, installRecordLastRoute, installServerUpgradeGrowl];
const navigationGuardInstallers = [installLoadInitialSettings, installAttemptFirstLogin, installAuthentication, installProducts, installClusters, installRuntimeExtensionRoute, installI18N, installHandleInstallRedirect, installPageTitle, installRecordLastRoute, installServerUpgradeGrowl, installRouteGuardUnregisteredRoutes];

navigationGuardInstallers.forEach((installer) => installer(router, context));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
export function install(router, context) {
router.afterEach((to, from, next) => handleRouteGuardUnregisteredRoutes(to, from, next, context));
}

/*
Guard to handle unregistered routes for dynamic products/resources.
If a route is not found for a given product/resource, redirect to 404.

Especially useful for extensions.
*/
export async function handleRouteGuardUnregisteredRoutes(to, from, next, { store }) {
const product = to.params?.product || to.meta?.product;
const resource = to.params?.resource;

if (product) {
// get all types for the product
const allTypes = store.getters['type-map/allTypes'](to.params?.product)?.all;

if (allTypes) {
let matchFound = false;

// check if resource matches any registered type via key
if (resource && allTypes[resource]) {
matchFound = true;
} else {
// fallback: check if route name matches any registered type route name
matchFound = Object.entries(allTypes).find(([key, value]) => value.route?.name === to.name);
}

if (!matchFound) {
return next({ name: '404' });
}
}
}
}
Loading