Skip to content

Commit ce0f1e4

Browse files
committed
#1591 added current search "filter" to URL params in order to make search bookmarkable and survive SSO redirects
* base64 encoded url_state
1 parent a165a43 commit ce0f1e4

8 files changed

+42
-23
lines changed

ui/modules/environments/authorization.ts

+13-7
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

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

170172
async function handleSingleSignOnCallback(urlSearchParams: URLSearchParams) {
171173
let environment = Environments.current();
172-
let sameProviderForMainAndDevops =
173-
environment.authSettings?.main?.oidc.provider == environment.authSettings?.devops?.oidc.provider;
174174
const oidcProviderId = urlSearchParams.get(URL_OIDC_PROVIDER) || environment.authSettings?.main?.oidc.provider;
175175
let oidcProvider: OidcProviderConfiguration = environment.authSettings.oidc.providers[oidcProviderId];
176176
const settings: UserManagerSettings = oidcProvider;
@@ -188,7 +188,7 @@ async function handleSingleSignOnCallback(urlSearchParams: URLSearchParams) {
188188
environment.authSettings.devops.method = AuthMethod.oidc
189189
environment.authSettings.devops.oidc.bearerToken = user[oidcProvider.extractBearerTokenFrom]
190190
}
191-
window.history.replaceState(null, null, `${settings.redirect_uri}?${user.url_state}`)
191+
window.history.replaceState(null, null, `${settings.redirect_uri}?${atob(user.url_state)}`)
192192
await Environments.environmentsJsonChanged(false)
193193
}
194194
} catch (e) {
@@ -235,7 +235,7 @@ async function performSingleSignOn(forMainAuth: boolean): Promise<boolean> {
235235
mainAuth: forMainAuth || sameProviderForMainAndDevops,
236236
devopsAuth: !forMainAuth || sameProviderForMainAndDevops
237237
}),
238-
url_state: urlSearchParams.toString()
238+
url_state: btoa(urlSearchParams.toString()) // base64 encode to also support e.g. "&"
239239
});
240240
} catch (e) {
241241
showError(e)
@@ -260,7 +260,7 @@ async function performSingleSignOut(oidc: OidcAuthSettings) {
260260
postLogoutRedirectUri = settings.post_logout_redirect_uri;
261261
} else {
262262
// otherwise, build it dynamically, injecting the current urlSearchParams as query:
263-
`${settings.redirect_uri}?${urlSearchParams.toString()}`
263+
postLogoutRedirectUri = `${settings.redirect_uri}?${urlSearchParams.toString()}`
264264
}
265265
await userManager.signoutRedirect({
266266
post_logout_redirect_uri: postLogoutRedirectUri
@@ -334,16 +334,22 @@ export async function onEnvironmentChanged(initialPageLoad: boolean) {
334334
environment.authSettings?.main?.oidc?.autoSso === true
335335
) {
336336
await performSingleSignOn(true);
337-
await Environments.environmentsJsonChanged(false);
337+
Environments.saveEnvironmentsToLocalStorage();
338338
} else if (initialPageLoad &&
339339
environment.authSettings?.devops?.method === AuthMethod.oidc &&
340340
environment.authSettings?.devops?.oidc?.autoSso === true
341341
) {
342342
await performSingleSignOn(false);
343-
await Environments.environmentsJsonChanged(false);
343+
Environments.saveEnvironmentsToLocalStorage();
344344
} else if (isSsoCallbackRequest(urlSearchParams)) {
345345
await handleSingleSignOnCallback(urlSearchParams);
346346
}
347347

348348
API.setAuthHeader(_forDevops);
349+
350+
let filter = urlSearchParams.get('filter');
351+
if (filter) {
352+
ThingsSearchGlobalVars.lastSearch = filter;
353+
fillSearchFilterEdit(filter);
354+
}
349355
}

ui/modules/environments/environmentTemplates.json

-1
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,6 @@
173173
"authority": "http://localhost:9900/fake",
174174
"client_id": "some-client-id",
175175
"redirect_uri": "http://localhost:8000",
176-
"post_logout_redirect_uri": "http://localhost:8000",
177176
"response_type": "code",
178177
"scope": "openid"
179178
}

ui/modules/environments/environments.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ export async function environmentsJsonChanged(initialPageLoad: boolean, modified
316316
if (!activeEnvironment && oidcState !== null) {
317317
let stateAndUrlState = oidcState.split(";");
318318
if (stateAndUrlState.length > 1) {
319-
const urlState = stateAndUrlState[1];
319+
const urlState = atob(stateAndUrlState[1]); // base64 decode to also support e.g. "&"
320320
const preservedQueryParams = new URLSearchParams(urlState)
321321
const primaryEnvironmentName = preservedQueryParams.get(URL_PRIMARY_ENVIRONMENT_NAME);
322322
const oidcProvider = preservedQueryParams.get(URL_OIDC_PROVIDER);

ui/modules/things/featureMessages.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ function messageFeature() {
146146
}
147147

148148
function onEnvironmentChanged(modifiedField) {
149-
Environments.current()['messageTemplates'] = Environments.current()['messageTemplates'] || {};
149+
Environments.current().messageTemplates = Environments.current().messageTemplates || {};
150150

151151
if (!modifiedField) {
152152
clearAllFields();

ui/modules/things/fields.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ function toggleFieldSelection(fieldIndex: number) {
155155
* Callback on environment change. Initializes all UI components for fields
156156
*/
157157
function onEnvironmentChanged() {
158-
if (!Environments.current()['fieldList']) {
158+
if (!Environments.current().fieldList) {
159159
Environments.current().fieldList = [];
160160
}
161161
updateFieldList();

ui/modules/things/searchFilter.ts

+11-5
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,17 @@ export async function ready() {
5757
autoCompleteJS = Utils.createAutoComplete('#searchFilterEdit', createFilterList, 'Search for Things...');
5858
autoCompleteJS.input.addEventListener('selection', (event) => {
5959
const selection = event.detail.selection.value;
60-
fillSearchFilterEdit(selection.rql);
60+
fillSearchFilterEditAndSearch(selection.rql);
6161
});
6262

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

67-
dom.searchFavorite.onclick = () => {
68-
if (toggleFilterFavorite(dom.searchFilterEdit.value)) {
67+
dom.searchFavorite.onclick = async (e) => {
68+
e.preventDefault();
69+
let isValidQuery = await toggleFilterFavorite(dom.searchFilterEdit.value);
70+
if (isValidQuery) {
6971
dom.favIcon.classList.toggle('bi-star');
7072
dom.favIcon.classList.toggle('bi-star-fill');
7173
}
@@ -99,8 +101,12 @@ function onEnvironmentChanged() {
99101
}
100102
}
101103

102-
function fillSearchFilterEdit(fillString: string) {
104+
export function fillSearchFilterEdit(fillString: string) {
103105
dom.searchFilterEdit.value = fillString;
106+
}
107+
108+
export function fillSearchFilterEditAndSearch(fillString: string) {
109+
fillSearchFilterEdit(fillString);
104110

105111
checkIfFavorite();
106112
const filterEditNeeded = Utils.checkAndMarkInInput(dom.searchFilterEdit, FILTER_PLACEHOLDER);
@@ -179,7 +185,7 @@ async function createFilterList(query) {
179185
* @param {string} filter filter
180186
* @return {boolean} true if the filter was toggled
181187
*/
182-
async function toggleFilterFavorite(filter: string) {
188+
async function toggleFilterFavorite(filter: string): Promise<boolean> {
183189
if (!filter || filter === '') {
184190
return false;
185191
}

ui/modules/things/thingMessages.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ function messageThing() {
138138
}
139139

140140
function onEnvironmentChanged(modifiedField) {
141-
Environments.current()['messageTemplates'] = Environments.current()['messageTemplates'] || {};
141+
Environments.current().messageTemplates = Environments.current().messageTemplates || {};
142142

143143
if (!modifiedField) {
144144
clearAllFields();

ui/modules/things/thingsSearch.ts

+14-6
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ import * as Fields from './fields.js';
2626
import * as Things from './things.js';
2727
import * as ThingsSSE from './thingsSSE.js';
2828

29-
let lastSearch = '';
29+
export class ThingsSearchGlobalVars {
30+
public static lastSearch = '';
31+
}
32+
3033
let theSearchCursor;
3134

3235
const dom = {
@@ -79,35 +82,40 @@ function onThingsTableClicked(event) {
7982
* @param rqlFilterCallback a callback to invoke when the passed `filter` was a valid RQL statement
8083
*/
8184
export function searchTriggered(filter: string, rqlFilterCallback: () => void) {
82-
lastSearch = filter;
85+
ThingsSearchGlobalVars.lastSearch = filter;
8386
const regex = /^(eq\(|ne\(|gt\(|ge\(|lt\(|le\(|in\(|like\(|ilike\(|exists\(|and\(|or\(|not\().*/;
8487
if (filter === '' || regex.test(filter)) {
8588
searchThings(filter);
8689
rqlFilterCallback();
8790
} else {
8891
getThings([filter]);
8992
}
93+
let urlSearchParams = new URLSearchParams(window.location.search);
94+
if (urlSearchParams.get('filter') !== filter) {
95+
urlSearchParams.set('filter', filter);
96+
window.history.replaceState(null, null, `${window.location.pathname}?${urlSearchParams}`);
97+
}
9098
}
9199

92100
/**
93101
* Gets the list of pinned things
94102
*/
95103
export function pinnedTriggered() {
96-
lastSearch = 'pinned';
104+
ThingsSearchGlobalVars.lastSearch = 'pinned';
97105
dom.searchFilterEdit.value = null;
98106
dom.favIcon.classList.replace('bi-star-fill', 'bi-star');
99-
getThings(Environments.current()['pinnedThings']);
107+
getThings(Environments.current().pinnedThings);
100108
}
101109

102110
/**
103111
* Performs the last search by the user using the last used filter.
104112
* If the user used pinned things last time, the pinned things are reloaded
105113
*/
106114
export function performLastSearch() {
107-
if (lastSearch === 'pinned') {
115+
if (ThingsSearchGlobalVars.lastSearch === 'pinned') {
108116
pinnedTriggered();
109117
} else {
110-
searchTriggered(lastSearch, () => null);
118+
searchTriggered(ThingsSearchGlobalVars.lastSearch, () => null);
111119
}
112120
}
113121

0 commit comments

Comments
 (0)