Skip to content

Commit

Permalink
#1591 added current search "filter" to URL params in order to make se…
Browse files Browse the repository at this point in the history
…arch bookmarkable and survive SSO redirects

* base64 encoded url_state
  • Loading branch information
thjaeckle committed Sep 30, 2024
1 parent a165a43 commit ce0f1e4
Show file tree
Hide file tree
Showing 8 changed files with 42 additions and 23 deletions.
20 changes: 13 additions & 7 deletions ui/modules/environments/authorization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

import { UserManager, UserManagerSettings } from 'oidc-client-ts';
import * as API from '../api.js';
import { fillSearchFilterEdit } from '../things/searchFilter';
import { ThingsSearchGlobalVars } from '../things/thingsSearch';
import * as Utils from '../utils.js';
import { showError, showInfoToast } from '../utils.js';
import authorizationHTML from './authorization.html';
Expand Down Expand Up @@ -169,8 +171,6 @@ function isSsoCallbackRequest(urlSearchParams?: URLSearchParams): boolean {

async function handleSingleSignOnCallback(urlSearchParams: URLSearchParams) {
let environment = Environments.current();
let sameProviderForMainAndDevops =
environment.authSettings?.main?.oidc.provider == environment.authSettings?.devops?.oidc.provider;
const oidcProviderId = urlSearchParams.get(URL_OIDC_PROVIDER) || environment.authSettings?.main?.oidc.provider;
let oidcProvider: OidcProviderConfiguration = environment.authSettings.oidc.providers[oidcProviderId];
const settings: UserManagerSettings = oidcProvider;
Expand All @@ -188,7 +188,7 @@ async function handleSingleSignOnCallback(urlSearchParams: URLSearchParams) {
environment.authSettings.devops.method = AuthMethod.oidc
environment.authSettings.devops.oidc.bearerToken = user[oidcProvider.extractBearerTokenFrom]
}
window.history.replaceState(null, null, `${settings.redirect_uri}?${user.url_state}`)
window.history.replaceState(null, null, `${settings.redirect_uri}?${atob(user.url_state)}`)
await Environments.environmentsJsonChanged(false)
}
} catch (e) {
Expand Down Expand Up @@ -235,7 +235,7 @@ async function performSingleSignOn(forMainAuth: boolean): Promise<boolean> {
mainAuth: forMainAuth || sameProviderForMainAndDevops,
devopsAuth: !forMainAuth || sameProviderForMainAndDevops
}),
url_state: urlSearchParams.toString()
url_state: btoa(urlSearchParams.toString()) // base64 encode to also support e.g. "&"
});
} catch (e) {
showError(e)
Expand All @@ -260,7 +260,7 @@ async function performSingleSignOut(oidc: OidcAuthSettings) {
postLogoutRedirectUri = settings.post_logout_redirect_uri;
} else {
// otherwise, build it dynamically, injecting the current urlSearchParams as query:
`${settings.redirect_uri}?${urlSearchParams.toString()}`
postLogoutRedirectUri = `${settings.redirect_uri}?${urlSearchParams.toString()}`
}
await userManager.signoutRedirect({
post_logout_redirect_uri: postLogoutRedirectUri
Expand Down Expand Up @@ -334,16 +334,22 @@ export async function onEnvironmentChanged(initialPageLoad: boolean) {
environment.authSettings?.main?.oidc?.autoSso === true
) {
await performSingleSignOn(true);
await Environments.environmentsJsonChanged(false);
Environments.saveEnvironmentsToLocalStorage();
} else if (initialPageLoad &&
environment.authSettings?.devops?.method === AuthMethod.oidc &&
environment.authSettings?.devops?.oidc?.autoSso === true
) {
await performSingleSignOn(false);
await Environments.environmentsJsonChanged(false);
Environments.saveEnvironmentsToLocalStorage();
} else if (isSsoCallbackRequest(urlSearchParams)) {
await handleSingleSignOnCallback(urlSearchParams);
}

API.setAuthHeader(_forDevops);

let filter = urlSearchParams.get('filter');
if (filter) {
ThingsSearchGlobalVars.lastSearch = filter;
fillSearchFilterEdit(filter);
}
}
1 change: 0 additions & 1 deletion ui/modules/environments/environmentTemplates.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,6 @@
"authority": "http://localhost:9900/fake",
"client_id": "some-client-id",
"redirect_uri": "http://localhost:8000",
"post_logout_redirect_uri": "http://localhost:8000",
"response_type": "code",
"scope": "openid"
}
Expand Down
2 changes: 1 addition & 1 deletion ui/modules/environments/environments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ export async function environmentsJsonChanged(initialPageLoad: boolean, modified
if (!activeEnvironment && oidcState !== null) {
let stateAndUrlState = oidcState.split(";");
if (stateAndUrlState.length > 1) {
const urlState = stateAndUrlState[1];
const urlState = atob(stateAndUrlState[1]); // base64 decode to also support e.g. "&"
const preservedQueryParams = new URLSearchParams(urlState)
const primaryEnvironmentName = preservedQueryParams.get(URL_PRIMARY_ENVIRONMENT_NAME);
const oidcProvider = preservedQueryParams.get(URL_OIDC_PROVIDER);
Expand Down
2 changes: 1 addition & 1 deletion ui/modules/things/featureMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ function messageFeature() {
}

function onEnvironmentChanged(modifiedField) {
Environments.current()['messageTemplates'] = Environments.current()['messageTemplates'] || {};
Environments.current().messageTemplates = Environments.current().messageTemplates || {};

if (!modifiedField) {
clearAllFields();
Expand Down
2 changes: 1 addition & 1 deletion ui/modules/things/fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ function toggleFieldSelection(fieldIndex: number) {
* Callback on environment change. Initializes all UI components for fields
*/
function onEnvironmentChanged() {
if (!Environments.current()['fieldList']) {
if (!Environments.current().fieldList) {
Environments.current().fieldList = [];
}
updateFieldList();
Expand Down
16 changes: 11 additions & 5 deletions ui/modules/things/searchFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,17 @@ export async function ready() {
autoCompleteJS = Utils.createAutoComplete('#searchFilterEdit', createFilterList, 'Search for Things...');
autoCompleteJS.input.addEventListener('selection', (event) => {
const selection = event.detail.selection.value;
fillSearchFilterEdit(selection.rql);
fillSearchFilterEditAndSearch(selection.rql);
});

dom.searchThings.onclick = () => {
ThingsSearch.searchTriggered(dom.searchFilterEdit.value, () => fillHistory(dom.searchFilterEdit.value));
};

dom.searchFavorite.onclick = () => {
if (toggleFilterFavorite(dom.searchFilterEdit.value)) {
dom.searchFavorite.onclick = async (e) => {
e.preventDefault();
let isValidQuery = await toggleFilterFavorite(dom.searchFilterEdit.value);
if (isValidQuery) {
dom.favIcon.classList.toggle('bi-star');
dom.favIcon.classList.toggle('bi-star-fill');
}
Expand Down Expand Up @@ -99,8 +101,12 @@ function onEnvironmentChanged() {
}
}

function fillSearchFilterEdit(fillString: string) {
export function fillSearchFilterEdit(fillString: string) {
dom.searchFilterEdit.value = fillString;
}

export function fillSearchFilterEditAndSearch(fillString: string) {
fillSearchFilterEdit(fillString);

checkIfFavorite();
const filterEditNeeded = Utils.checkAndMarkInInput(dom.searchFilterEdit, FILTER_PLACEHOLDER);
Expand Down Expand Up @@ -179,7 +185,7 @@ async function createFilterList(query) {
* @param {string} filter filter
* @return {boolean} true if the filter was toggled
*/
async function toggleFilterFavorite(filter: string) {
async function toggleFilterFavorite(filter: string): Promise<boolean> {
if (!filter || filter === '') {
return false;
}
Expand Down
2 changes: 1 addition & 1 deletion ui/modules/things/thingMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ function messageThing() {
}

function onEnvironmentChanged(modifiedField) {
Environments.current()['messageTemplates'] = Environments.current()['messageTemplates'] || {};
Environments.current().messageTemplates = Environments.current().messageTemplates || {};

if (!modifiedField) {
clearAllFields();
Expand Down
20 changes: 14 additions & 6 deletions ui/modules/things/thingsSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ import * as Fields from './fields.js';
import * as Things from './things.js';
import * as ThingsSSE from './thingsSSE.js';

let lastSearch = '';
export class ThingsSearchGlobalVars {
public static lastSearch = '';
}

let theSearchCursor;

const dom = {
Expand Down Expand Up @@ -79,35 +82,40 @@ function onThingsTableClicked(event) {
* @param rqlFilterCallback a callback to invoke when the passed `filter` was a valid RQL statement
*/
export function searchTriggered(filter: string, rqlFilterCallback: () => void) {
lastSearch = filter;
ThingsSearchGlobalVars.lastSearch = filter;
const regex = /^(eq\(|ne\(|gt\(|ge\(|lt\(|le\(|in\(|like\(|ilike\(|exists\(|and\(|or\(|not\().*/;
if (filter === '' || regex.test(filter)) {
searchThings(filter);
rqlFilterCallback();
} else {
getThings([filter]);
}
let urlSearchParams = new URLSearchParams(window.location.search);
if (urlSearchParams.get('filter') !== filter) {
urlSearchParams.set('filter', filter);
window.history.replaceState(null, null, `${window.location.pathname}?${urlSearchParams}`);
}
}

/**
* Gets the list of pinned things
*/
export function pinnedTriggered() {
lastSearch = 'pinned';
ThingsSearchGlobalVars.lastSearch = 'pinned';
dom.searchFilterEdit.value = null;
dom.favIcon.classList.replace('bi-star-fill', 'bi-star');
getThings(Environments.current()['pinnedThings']);
getThings(Environments.current().pinnedThings);
}

/**
* Performs the last search by the user using the last used filter.
* If the user used pinned things last time, the pinned things are reloaded
*/
export function performLastSearch() {
if (lastSearch === 'pinned') {
if (ThingsSearchGlobalVars.lastSearch === 'pinned') {
pinnedTriggered();
} else {
searchTriggered(lastSearch, () => null);
searchTriggered(ThingsSearchGlobalVars.lastSearch, () => null);
}
}

Expand Down

0 comments on commit ce0f1e4

Please sign in to comment.