diff --git a/src/App.js b/src/App.js index 8376431b189bcd3447684f2c77c62162d9e5549f..a4b12a20aa65c30dc526b5566749710be34aa599 100644 --- a/src/App.js +++ b/src/App.js @@ -1,6 +1,6 @@ import React from 'react'; -import { HashRouter, Switch, Route } from 'react-router-dom'; +import { BrowserRouter, Switch, Route } from 'react-router-dom'; import EntryPage from './components/entry-page/entry-page.js'; import ErrorDialog from './components/dialog/error-dialog.js'; @@ -13,7 +13,7 @@ class App extends React.Component { <div> <ErrorDialog /> <NotificationDialog/> - <HashRouter> + <BrowserRouter> <Switch> <Route path='/experiments-overview'> <ExperimentOverview /> @@ -22,7 +22,7 @@ class App extends React.Component { <EntryPage /> </Route> </Switch> - </HashRouter> + </BrowserRouter> </div> ); } diff --git a/src/services/authentication-service-v2.js b/src/services/authentication-service-v2.js new file mode 100644 index 0000000000000000000000000000000000000000..2548876ea73035b1bffd21a606ceb65b2f4c87e2 --- /dev/null +++ b/src/services/authentication-service-v2.js @@ -0,0 +1,157 @@ +import config from '../config.json'; + +let _instance = null; +const SINGLETON_ENFORCER = Symbol(); + +/** + * Service taking care of OIDC/FS authentication for NRP accounts + */ +class AuthenticationService { + constructor(enforcer) { + if (enforcer !== SINGLETON_ENFORCER) { + throw new Error('Use ' + this.constructor.name + '.instance'); + } + + this.CLIENT_ID = config.authV2.clientId; + this.STORAGE_KEY = `tokens-${this.CLIENT_ID}@https://iam.ebrains.eu/auth/realms/hbp/protocol/openid-connect/auth`; + + this.redirectToAuthPage = true; + this.checkForSessionStateAndAuthCode(); + //this.checkForNewTokenToStore(); + } + + static get instance() { + if (_instance == null) { + _instance = new AuthenticationService(SINGLETON_ENFORCER); + } + + return _instance; + } + + /** + * Checks if the current page URL contains access tokens. + * This happens when the successfully logging in at the proxy login page and + * being redirected back with the token info. + * Will automatically remove additional access info and present a clean URL after being redirected. + */ + checkForNewTokenToStore() { + const path = window.location.href; + const accessTokenMatch = /&access_token=([^&]*)/.exec(path); + + if (!accessTokenMatch || !accessTokenMatch[1]) { + return; + } + + let accessToken = accessTokenMatch[1]; + + localStorage.setItem( + this.STORAGE_KEY, + //eslint-disable-next-line camelcase + JSON.stringify([{ access_token: accessToken }]) + ); + const pathMinusAccessToken = path.substr(0, path.indexOf('&access_token=')); + window.location.href = pathMinusAccessToken; + } + + /** + * Checks if the current page URL contains access tokens. + * This happens when the successfully logging in at the proxy login page and + * being redirected back with the token info. + * Will automatically remove additional access info and present a clean URL after being redirected. + */ + checkForSessionStateAndAuthCode() { + const path = window.location.href; + const sessionStateMatch = /&session_state=([^&]*)/.exec(path); + const authCodeMatch = /&code=([^&]*)/.exec(path); + + if (!sessionStateMatch || !authCodeMatch[1]) { + return; + } + + this.redirectToAuthPage = false; + + let sessionState = sessionStateMatch[1]; + let authCode = authCodeMatch[1]; + console.info({sessionState: sessionState, authCode: authCode}); + + let urlRequestAccessToken = 'https://iam.ebrains.eu/auth/realms/hbp/protocol/openid-connect/token?' + + 'grant_type=authorization_code' + + '&client_id=' + this.CLIENT_ID + + '&redirect_uri=' + window.location.href + + '&code=' + authCode + + '&client_secret=' + 'some-secret'; + let responseAccessTokenRequest = this.httpRequestPOST(urlRequestAccessToken); + + /*localStorage.setItem( + this.STORAGE_KEY, + //eslint-disable-next-line camelcase + JSON.stringify([{ access_token: accessToken }]) + );*/ + //const pathMinusAccessToken = path.substr(0, path.indexOf('?')); + //window.location.href = pathMinusAccessToken; + } + + /** + * Clear currently stored access token. + */ + clearStoredToken() { + localStorage.removeItem(this.STORAGE_KEY); + } + + /** + * Get the stored access token. + * + * @return token The stored access token. Or strings identifying 'no-token' / 'malformed-token'. + */ + getStoredToken() { + let storedItem = localStorage.getItem(this.STORAGE_KEY); + if (!storedItem) { + // this token will be rejected by the server and the client will get a proper auth error + return AuthenticationService.CONSTANTS.NO_TOKEN; + } + + try { + let tokens = JSON.parse(storedItem); + return tokens.length ? tokens[tokens.length - 1].access_token : null; + } + catch (e) { + // this token will be rejected by the server and the client will get a proper auth error + return AuthenticationService.CONSTANTS.MALFORMED_TOKEN; + } + } + + /** + * Opens the proxy's authentication page. + * + * @param {*} url The URL of the authentication page. + * If not an absolute URL it is assumed to be a subpage of the proxy. + */ + openAuthenticationPage(url = config.authV2.url) { + if (!this.redirectToAuthPage) { + return; + } + + this.clearStoredToken(); + + let absoluteUrl = /^https?:\/\//i; + if (!absoluteUrl.test(url)) { + url = `https://${url}`; + } + /*window.location.href = `${url} + &client_id=${this.CLIENT_ID} + &redirect_uri=${encodeURIComponent(window.location.href)}`;*/ + + let testClientID = 'community-apps-tutorial'; + let testRedirectURI = window.location.href; //'http://localhost:3000'; + window.location.href = url + + '&client_id=' + testClientID + + '&redirect_uri=' + testRedirectURI; + } +} + +AuthenticationService.CONSTANTS = Object.freeze({ + MALFORMED_TOKEN: 'malformed-token', + NO_TOKEN: 'no-token' +}); + +export default AuthenticationService; diff --git a/src/services/http-service.js b/src/services/http-service.js index 28c898193548fb623208b338982bc385f825650c..30651bc6ef8efe5b3be2c6402bc6fa12610c165e 100644 --- a/src/services/http-service.js +++ b/src/services/http-service.js @@ -1,7 +1,7 @@ import { EventEmitter } from 'events'; -import AuthenticationService from './authentication-service.js'; +import AuthenticationService from './authentication-service-v2.js'; /** * Base class that performs http requests with default request options. @@ -55,7 +55,9 @@ export class HttpService extends EventEmitter { if (!response.ok) { if (response.status === 477) { const responseText = await response.text(); - AuthenticationService.instance.openAuthenticationPage(responseText); + console.info('auth error'); + console.info(responseText); + AuthenticationService.instance.openAuthenticationPage(/*responseText*/); } else if (response.status === 478) { //TODO: redirect to maintenance page diff --git a/src/services/proxy/nrp-user-service.js b/src/services/proxy/nrp-user-service.js index 9801ef00e73d8fb9f06f8a02e86b0c82d1abb9b6..94f0024d02737c0e4bf7b0ccab61d11a74cdd3d0 100644 --- a/src/services/proxy/nrp-user-service.js +++ b/src/services/proxy/nrp-user-service.js @@ -61,7 +61,11 @@ class NrpUserService extends HttpService { */ async getCurrentUser() { if (!this.currentUser) { - this.currentUser = await (await this.httpRequestGET(IDENTITY_ME_URL)).json(); + let responseIdentity = await this.httpRequestGET(IDENTITY_ME_URL); + console.info(responseIdentity); + if (responseIdentity.ok) { + this.currentUser = await responseIdentity.json(); + } } return this.currentUser;