diff --git a/gui/src/components/search/SearchContext.js b/gui/src/components/search/SearchContext.js index 9c11f1c59520fac79a5e1c952cd81f6a12ea0422..8b3ee59e2146b0d6c3a0799c1d9fd0ea569505e2 100644 --- a/gui/src/components/search/SearchContext.js +++ b/gui/src/components/search/SearchContext.js @@ -1,4 +1,3 @@ -/* eslint-disable no-unused-vars */ /* * Copyright The NOMAD Authors. * @@ -98,6 +97,16 @@ function parseQueryString(queryString) { : undefined } +/** + * Removes keycloak-specific fragments from the given URL. +*/ +function removeKeycloak(url) { + for (const pattern of ['#state=', '#iss=', '#error=']) { + url = url.split(pattern)[0] + } + return url +} + const RouteListener = React.memo(() => { const location = useLocation() const history = useHistory() @@ -119,7 +128,7 @@ const RouteListener = React.memo(() => { // the syntax for data within custom schemas uses a hashtag. TODO: Keycloak // seems to use the hashtag for special purposes as well, which causes some // clashes when loading the page from scratch. - const queryString = location.search.slice(1) + location.hash.startsWith("#state=") ? '' : location.hash + const queryString = removeKeycloak(location.search.slice(1) + location.hash) // Empty query string preserves the old filters, as this will enable people // to preserve filters when changing apps. This causes minor // inconsistencies, but might be better for the UX. @@ -143,13 +152,15 @@ export const withQueryString = (WrappedComponent) => { const firstRender = useRef(true) const valueRef = useRef() + // Extract URL query parameters, ignore keycloak authentication state + // parameters starting with '#iss='. useMemo(() => { if (firstRender.current) { const split = window.location.href.split('?') - const search = (split.length !== 1) - ? split.pop() + const search = (split.length > 1) + ? parseQueryString(removeKeycloak(split.slice(-1)[0])) : {} - valueRef.current = {...(initialFilterValues || {}), ...parseQueryString(search)} + valueRef.current = {...(initialFilterValues || {}), ...search} firstRender.current = false } }, [initialFilterValues]) diff --git a/gui/src/components/search/SearchContext.spec.js b/gui/src/components/search/SearchContext.spec.js index a98aaac87dcefd1f380c0e25aaa75ac041814a0f..46740c663efb8da2bf8744f1b8a9334fb925a910 100644 --- a/gui/src/components/search/SearchContext.spec.js +++ b/gui/src/components/search/SearchContext.spec.js @@ -21,6 +21,7 @@ import { useSearchContext } from './SearchContext' import { Quantity } from '../units/Quantity' import { isEqualWith } from 'lodash' import { SearchSuggestion, SuggestionType } from './SearchSuggestion' +import { WrapperDefault } from '../conftest.spec' describe('parseQuery', function() { test.each([ @@ -82,3 +83,29 @@ describe('suggestions', function() { expect(suggestions[0].key).toBe(newSuggestion.key) }) }) + +describe('reading query from URL', function() { + test.each([ + ['no query parameters', '', {}], + ['query parameter', '?upload_id=testing', {upload_id: new Set(['testing'])}], + ['query parameter with keycloak iss', '?upload_id=testing#iss=https://nomad-lab.eu/fairdi/keycloak/auth/realms/fairdi_nomad_prod', {upload_id: new Set(['testing'])}], + ['query parameter with keycloak error', '?upload_id=testing#error=login_required', {upload_id: new Set(['testing'])}], + ['query parameter with keycloak state', '?upload_id=testing#state=here is a state', {upload_id: new Set(['testing'])}] + ])('%s', async (name, params, expected_query) => { + // Set window.location.href + // eslint-disable-next-line no-global-assign + window = Object.create(window) + const url = "http://testing.com" + params + Object.defineProperty(window, 'location', { + value: {href: url}, + writable: true + }) + + // Call hooks to check that query is correctly read + const { result: resultUseSearchContext } = renderHook(() => useSearchContext(), { wrapper: WrapperSearch }) + const { result: resultUseQuery } = renderHook(() => resultUseSearchContext.current.useQuery(), { wrapper: WrapperDefault}) + const query = resultUseQuery.current + console.log(query) + expect(query).toMatchObject(expected_query) + }) +})