diff --git a/web/client/actions/__tests__/login-test.js b/web/client/actions/__tests__/login-test.js index 1537121c4c..af16d9dc8c 100644 --- a/web/client/actions/__tests__/login-test.js +++ b/web/client/actions/__tests__/login-test.js @@ -17,7 +17,7 @@ describe('login actions', () => { it('default with provider', () => { let page; const PROVIDER = "google"; - openIDLogin({provider: PROVIDER}, (p) => {page = p;} )(); + openIDLogin({provider: PROVIDER, loginRedirectHash: false}, (p) => {page = p;} )(); const geostore = ConfigUtils.getConfigProp("geoStoreUrl"); expect(page).toEqual(`${geostore}openid/${PROVIDER}/login`); }); @@ -25,7 +25,7 @@ describe('login actions', () => { let page; const PROVIDER = "google"; const TEST_URL = "/test/path"; - openIDLogin({provider: PROVIDER, url: TEST_URL}, (p) => {page = p;} )(); + openIDLogin({provider: PROVIDER, url: TEST_URL, loginRedirectHash: false}, (p) => {page = p;} )(); expect(page).toEqual(TEST_URL); }); }); diff --git a/web/client/actions/login.js b/web/client/actions/login.js index f4f97e7b3e..801a8b4be9 100644 --- a/web/client/actions/login.js +++ b/web/client/actions/login.js @@ -9,7 +9,7 @@ import ConfigUtils from '../utils/ConfigUtils'; import { setControlProperty } from './controls'; -import { logoutWithReload, resetError } from './security'; +import { logoutWithReload, resetError, LOGIN_REDIRECT_KEY } from './security'; import AuthenticationAPI from '../api/GeoStoreDAO'; /** @@ -17,12 +17,18 @@ import AuthenticationAPI from '../api/GeoStoreDAO'; * @param {object} entry the provider entry to use for login * @param {string} entry.provider the name of the provider configured (e.g. google, keycloak, ...) * @param {string} entry.url URL of the entry to use to login. If not passed, `/openid//login`. + * @param {boolean} [entry.loginRedirectHash=true] if true, the hash of the current page is stored in sessionStorage to redirect after login. Notice: (this has been introduced for tests, where it should be set to false, to prevent the loginSuccess o fail) * @param {function} goToPage redirect function, useful to mock for testing. * @returns {function} the think to execute. It doesn't dispatch any action, but sets a cookie to remember the authProvider used. * @memberof actions.login */ export function openIDLogin(entry, goToPage = (page) => {window.location.href = page; }) { return () => { + if (entry.loginRedirectHash ?? true) { + alert("You are being redirected to the login page. Please wait..."); + debugger; + sessionStorage.setItem(LOGIN_REDIRECT_KEY, window.location.hash); // store the hash to redirect after login + } goToPage(entry?.url ?? `${ ConfigUtils.getConfigProp("geoStoreUrl")}openid/${entry?.provider}/login`); }; } diff --git a/web/client/actions/security.js b/web/client/actions/security.js index 25522cef46..0cd021b82e 100644 --- a/web/client/actions/security.js +++ b/web/client/actions/security.js @@ -10,6 +10,7 @@ * Here you can change the API to use for AuthenticationAPI */ import AuthenticationAPI from '../api/GeoStoreDAO'; +import { push } from 'connected-react-router'; import {setCredentials, getToken, getRefreshToken} from '../utils/SecurityUtils'; import {encodeUTF8} from '../utils/EncodeUtils'; @@ -34,8 +35,21 @@ export const SET_CREDENTIALS = 'SECURITY:SET_CREDENTIALS'; export const CLEAR_SECURITY = 'SECURITY:CLEAR_SECURITY'; export const SET_PROTECTED_SERVICES = 'SECURITY:SET_PROTECTED_SERVICES'; export const REFRESH_SECURITY_LAYERS = 'SECURITY:REFRESH_SECURITY_LAYERS'; + +export const LOGIN_REDIRECT_KEY = 'loginRedirectHash'; // key used to store the redirect hash in sessionStorage +/** + * Action dispatched when the user successfully logs in. + * Note: It will also redirect to the hash stored in sessionStorage, if available. This because in case of external login (e.g. OpenID Connect), + * the login is done in a different page, and the redirect url as hash is not managed by the authentication API. + * @param {object} userDetails the user details returned by the authentication API + * @param {string} username the username. + * @param {string} password the password. + * @param {string} authProvider the auth provider used for authentication. + * @returns {object|function} the action to dispatch on successful login. + * If a redirect hash is stored in sessionStorage, will return a function, becoming a thunk. It will redirect to that hash after dispatch + */ export function loginSuccess(userDetails, username, password, authProvider) { - return { + const loginAction = { type: LOGIN_SUCCESS, userDetails: userDetails, // set here for compatibility reasons @@ -45,6 +59,17 @@ export function loginSuccess(userDetails, username, password, authProvider) { password: password, authProvider: authProvider }; + const hash = sessionStorage.getItem(LOGIN_REDIRECT_KEY); + if (hash && hash !== window.location.hash) { + sessionStorage.removeItem(LOGIN_REDIRECT_KEY); + // creating the thunk only in this case preserves + // the original action for testing purposes + return (dispatch) => { + dispatch(loginAction); + dispatch(push(hash.split('#')[1])); + }; + } + return loginAction; } export function loginFail(e) { diff --git a/web/client/plugins/__tests__/Login-test.js b/web/client/plugins/__tests__/Login-test.js index 5e2c2b8cd5..a2deb94da6 100644 --- a/web/client/plugins/__tests__/Login-test.js +++ b/web/client/plugins/__tests__/Login-test.js @@ -186,7 +186,7 @@ describe('Login Plugin', () => { goToPage: () => {} }; expect.spyOn(spyOn, 'goToPage'); - ConfigUtils.setConfigProp("authenticationProviders", [{type: "openID", provider: "oidc", goToPage: spyOn.goToPage}]); // goToPage is normally empty, but can be used to mock the redirect in tests + ConfigUtils.setConfigProp("authenticationProviders", [{type: "openID", provider: "oidc", loginRedirectHash: false, goToPage: spyOn.goToPage}]); // goToPage is normally empty, but can be used to mock the redirect in tests const { Plugin } = getPluginForTest(Login, {}); const { Plugin: OmniBarPlugin } = getPluginForTest(OmniBar, {}, { LoginPlugin: Login }); @@ -198,7 +198,7 @@ describe('Login Plugin', () => { expect(spyOn.goToPage.calls[0].arguments[0]).toEqual(`/rest/geostore/openid/oidc/login`); }); it('openID with userInfo configured', () => { - ConfigUtils.setConfigProp("authenticationProviders", [{type: "openID", provider: "google", showAccountInfo: true}]); + ConfigUtils.setConfigProp("authenticationProviders", [{type: "openID", provider: "google", loginRedirectHash: false, showAccountInfo: true}]); const storeState = stateMocker(toggleControl('LoginForm', 'enabled'), loginSuccess({ User: { name: "Test", access_token: "some-token" }, authProvider: "google"}) ); const { Plugin } = getPluginForTest(Login, storeState); const { Plugin: OmniBarPlugin } = getPluginForTest(OmniBar, storeState, { LoginPlugin: Login }); diff --git a/web/client/utils/__tests__/KeycloakUtils-test.js b/web/client/utils/__tests__/KeycloakUtils-test.js index c438981f6f..bc3d6fbb5c 100644 --- a/web/client/utils/__tests__/KeycloakUtils-test.js +++ b/web/client/utils/__tests__/KeycloakUtils-test.js @@ -115,7 +115,7 @@ describe('KeycloakUtils', () => { }); }); it('keycloak login', (done) => { - const provider = {...PROVIDER, goToPage: () => {}}; + const provider = {...PROVIDER, loginRedirectHash: false, goToPage: () => {}}; getKeycloakClient(provider).then((kc) => { const epic = monitorKeycloak(provider); const initSpy = expect.spyOn(kc, "init").andCallThrough();