diff --git a/src/components/Login/LoginCtrl.js b/src/components/Login/LoginCtrl.js index 0d49ebb1a..1da375663 100644 --- a/src/components/Login/LoginCtrl.js +++ b/src/components/Login/LoginCtrl.js @@ -35,9 +35,9 @@ class LoginCtrl extends Component { constructor(props) { super(props); - this.sys = require('stripes-config'); // eslint-disable-line global-require - this.okapiUrl = this.sys.okapi.url; - this.tenant = this.sys.okapi.tenant; + // store is already available on login page with okapi data + this.okapiUrl = this.constructor.contextType.store.getState().okapi.url; + this.tenant = this.constructor.contextType.store.getState().okapi.tenant; if (props.autoLogin && props.autoLogin.username) { this.handleSubmit(props.autoLogin); } diff --git a/src/components/Root/FFetch.js b/src/components/Root/FFetch.js index 658139dc2..7b0563a43 100644 --- a/src/components/Root/FFetch.js +++ b/src/components/Root/FFetch.js @@ -42,7 +42,6 @@ */ import ms from 'ms'; -import { okapi as okapiConfig } from 'stripes-config'; import { setRtrTimeout, setRtrFlsTimeout, @@ -77,10 +76,11 @@ const OKAPI_FETCH_OPTIONS = { }; export class FFetch { - constructor({ logger, store, rtrConfig }) { + constructor({ logger, store, rtrConfig, okapi }) { this.logger = logger; this.store = store; this.rtrConfig = rtrConfig; + this.okapi = okapi; } /** @@ -232,12 +232,12 @@ export class FFetch { */ ffetch = async (resource, options = {}) => { // FOLIO API requests are subject to RTR - if (isFolioApiRequest(resource, okapiConfig.url)) { + if (isFolioApiRequest(resource, this.okapi.url)) { this.logger.log('rtrv', 'will fetch', resource); // on authentication, grab the response to kick of the rotation cycle, // then return the response - if (isAuthenticationRequest(resource, okapiConfig.url)) { + if (isAuthenticationRequest(resource, this.okapi.url)) { this.logger.log('rtr', 'authn request', resource); return this.nativeFetch.apply(global, [resource, options && { ...options, ...OKAPI_FETCH_OPTIONS }]) .then(res => { @@ -266,7 +266,7 @@ export class FFetch { // tries to logout, the logout request will fail. And that's fine, just // fine. We will let them fail, capturing the response and swallowing it // to avoid getting stuck in an error loop. - if (isLogoutRequest(resource, okapiConfig.url)) { + if (isLogoutRequest(resource, this.okapi.url)) { this.logger.log('rtr', 'logout request'); return this.nativeFetch.apply(global, [resource, options && { ...options, ...OKAPI_FETCH_OPTIONS }]) diff --git a/src/components/Root/FFetch.test.js b/src/components/Root/FFetch.test.js index 461414e91..2c486b55f 100644 --- a/src/components/Root/FFetch.test.js +++ b/src/components/Root/FFetch.test.js @@ -20,16 +20,6 @@ jest.mock('../../loginServices', () => ({ getTokenExpiry: jest.fn(() => Promise.resolve()) })); -jest.mock('stripes-config', () => ({ - url: 'okapiUrl', - tenant: 'okapiTenant', - okapi: { - url: 'okapiUrl', - tenant: 'okapiTenant' - } -}), -{ virtual: true }); - const log = jest.fn(); const mockFetch = jest.fn(); @@ -50,7 +40,11 @@ describe('FFetch class', () => { describe('Calling a non-FOLIO API', () => { it('calls native fetch once', async () => { mockFetch.mockResolvedValueOnce('non-okapi-success'); - const testFfetch = new FFetch({ logger: { log } }); + const testFfetch = new FFetch({ logger: { log }, + okapi:{ + url: 'okapiUrl', + tenant: 'okapiTenant' + } }); testFfetch.replaceFetch(); testFfetch.replaceXMLHttpRequest(); @@ -63,7 +57,11 @@ describe('FFetch class', () => { describe('Calling a FOLIO API fetch', () => { it('calls native fetch once', async () => { mockFetch.mockResolvedValueOnce('okapi-success'); - const testFfetch = new FFetch({ logger: { log } }); + const testFfetch = new FFetch({ logger: { log }, + okapi:{ + url: 'okapiUrl', + tenant: 'okapiTenant' + } }); testFfetch.replaceFetch(); testFfetch.replaceXMLHttpRequest(); const response = await global.fetch('okapiUrl/whatever', { testOption: 'test' }); @@ -91,6 +89,10 @@ describe('FFetch class', () => { logger: { log }, store: { dispatch: jest.fn(), + }, + okapi:{ + url: 'okapiUrl', + tenant: 'okapiTenant' } }); testFfetch.replaceFetch(); @@ -110,7 +112,11 @@ describe('FFetch class', () => { describe('logging out', () => { it('calls native fetch once to log out', async () => { mockFetch.mockResolvedValueOnce('logged out'); - const testFfetch = new FFetch({ logger: { log } }); + const testFfetch = new FFetch({ logger: { log }, + okapi:{ + url: 'okapiUrl', + tenant: 'okapiTenant' + } }); testFfetch.replaceFetch(); testFfetch.replaceXMLHttpRequest(); @@ -124,7 +130,11 @@ describe('FFetch class', () => { it('fetch failure is silently trapped', async () => { mockFetch.mockRejectedValueOnce('logged out FAIL'); - const testFfetch = new FFetch({ logger: { log } }); + const testFfetch = new FFetch({ logger: { log }, + okapi:{ + url: 'okapiUrl', + tenant: 'okapiTenant' + } }); testFfetch.replaceFetch(); testFfetch.replaceXMLHttpRequest(); @@ -143,7 +153,11 @@ describe('FFetch class', () => { describe('logging out', () => { it('Calling an okapi fetch with valid token...', async () => { mockFetch.mockResolvedValueOnce('okapi success'); - const testFfetch = new FFetch({ logger: { log } }); + const testFfetch = new FFetch({ logger: { log }, + okapi:{ + url: 'okapiUrl', + tenant: 'okapiTenant' + } }); testFfetch.replaceFetch(); testFfetch.replaceXMLHttpRequest(); @@ -188,6 +202,10 @@ describe('FFetch class', () => { rtrConfig: { fixedLengthSessionWarningTTL: '1m', }, + okapi:{ + url: 'okapiUrl', + tenant: 'okapiTenant' + } }); testFfetch.replaceFetch(); testFfetch.replaceXMLHttpRequest(); @@ -244,6 +262,10 @@ describe('FFetch class', () => { rtrConfig: { fixedLengthSessionWarningTTL: '1m', }, + okapi:{ + url: 'okapiUrl', + tenant: 'okapiTenant' + } }); testFfetch.replaceFetch(); testFfetch.replaceXMLHttpRequest(); @@ -282,6 +304,10 @@ describe('FFetch class', () => { rtrConfig: { fixedLengthSessionWarningTTL: '1m', }, + okapi:{ + url: 'okapiUrl', + tenant: 'okapiTenant' + } }); testFfetch.replaceFetch(); testFfetch.replaceXMLHttpRequest(); @@ -317,6 +343,10 @@ describe('FFetch class', () => { logger: { log }, store: { dispatch: jest.fn(), + }, + okapi:{ + url: 'okapiUrl', + tenant: 'okapiTenant' } }); testFfetch.replaceFetch(); @@ -361,6 +391,10 @@ describe('FFetch class', () => { rtrConfig: { fixedLengthSessionWarningTTL: '1m', }, + okapi:{ + url: 'okapiUrl', + tenant: 'okapiTenant' + } }); testFfetch.replaceFetch(); testFfetch.replaceXMLHttpRequest(); @@ -389,7 +423,11 @@ describe('FFetch class', () => { it('returns the error', async () => { mockFetch.mockResolvedValue('success') .mockResolvedValueOnce('failure'); - const testFfetch = new FFetch({ logger: { log } }); + const testFfetch = new FFetch({ logger: { log }, + okapi:{ + url: 'okapiUrl', + tenant: 'okapiTenant' + } }); testFfetch.replaceFetch(); testFfetch.replaceXMLHttpRequest(); @@ -411,7 +449,11 @@ describe('FFetch class', () => { }, } )); - const testFfetch = new FFetch({ logger: { log } }); + const testFfetch = new FFetch({ logger: { log }, + okapi:{ + url: 'okapiUrl', + tenant: 'okapiTenant' + } }); testFfetch.replaceFetch(); testFfetch.replaceXMLHttpRequest(); @@ -435,7 +477,11 @@ describe('FFetch class', () => { } )) .mockRejectedValueOnce(new Error('token error message')); - const testFfetch = new FFetch({ logger: { log } }); + const testFfetch = new FFetch({ logger: { log }, + okapi:{ + url: 'okapiUrl', + tenant: 'okapiTenant' + } }); testFfetch.replaceFetch(); testFfetch.replaceXMLHttpRequest(); @@ -470,7 +516,11 @@ describe('FFetch class', () => { } } )); - const testFfetch = new FFetch({ logger: { log } }); + const testFfetch = new FFetch({ logger: { log }, + okapi:{ + url: 'okapiUrl', + tenant: 'okapiTenant' + } }); testFfetch.replaceFetch(); testFfetch.replaceXMLHttpRequest(); @@ -500,7 +550,11 @@ describe('FFetch class', () => { } } )); - const testFfetch = new FFetch({ logger: { log } }); + const testFfetch = new FFetch({ logger: { log }, + okapi:{ + url: 'okapiUrl', + tenant: 'okapiTenant' + } }); testFfetch.replaceFetch(); testFfetch.replaceXMLHttpRequest(); @@ -530,7 +584,11 @@ describe('FFetch class', () => { } } )); - const testFfetch = new FFetch({ logger: { log } }); + const testFfetch = new FFetch({ logger: { log }, + okapi:{ + url: 'okapiUrl', + tenant: 'okapiTenant' + } }); testFfetch.replaceFetch(); testFfetch.replaceXMLHttpRequest(); diff --git a/src/components/Root/FXHR.js b/src/components/Root/FXHR.js index c5232fc97..c4f189d81 100644 --- a/src/components/Root/FXHR.js +++ b/src/components/Root/FXHR.js @@ -1,4 +1,3 @@ -import { okapi } from 'stripes-config'; import { getPromise, isFolioApiRequest } from './token-util'; import { RTR_ERROR_EVENT, @@ -15,7 +14,7 @@ export default (deps) => { open = (method, url) => { this.FFetchContext.logger?.log('rtr', 'capture XHR.open'); - this.shouldEnsureToken = isFolioApiRequest(url, okapi.url); + this.shouldEnsureToken = isFolioApiRequest(url, this.FFetchContext.okapi.url); super.open(method, url); } diff --git a/src/components/Root/FXHR.test.js b/src/components/Root/FXHR.test.js index 5c52ee602..712d9aa51 100644 --- a/src/components/Root/FXHR.test.js +++ b/src/components/Root/FXHR.test.js @@ -24,7 +24,7 @@ describe('FXHR', () => { let testXHR; beforeEach(() => { jest.clearAllMocks(); - FakeXHR = FXHR({ tokenExpiration: { atExpires: Date.now(), rtExpires: Date.now() + 5000 }, logger: { log: () => {} } }); + FakeXHR = FXHR({ tokenExpiration: { atExpires: Date.now(), rtExpires: Date.now() + 5000 }, logger: { log: () => {} }, okapi:{ url:'okapiUrl' } }); testXHR = new FakeXHR(); }); diff --git a/src/components/Root/Root.js b/src/components/Root/Root.js index caf9680dd..36ebbcccf 100644 --- a/src/components/Root/Root.js +++ b/src/components/Root/Root.js @@ -73,6 +73,7 @@ class Root extends Component { logger: this.props.logger, store, rtrConfig, + okapi }); this.ffetch.replaceFetch(); this.ffetch.replaceXMLHttpRequest(); diff --git a/src/components/SSOLanding/useSSOSession.js b/src/components/SSOLanding/useSSOSession.js index 8e71b01c4..9290917e6 100644 --- a/src/components/SSOLanding/useSSOSession.js +++ b/src/components/SSOLanding/useSSOSession.js @@ -4,7 +4,7 @@ import { useLocation } from 'react-router-dom'; import { useCookies } from 'react-cookie'; import queryString from 'query-string'; -import { config, okapi } from 'stripes-config'; +import { config } from 'stripes-config'; import { defaultErrors } from '../../constants'; import { setAuthError } from '../../okapiActions'; @@ -45,7 +45,7 @@ const useSSOSession = () => { const tenant = getTenant(params, token, store); useEffect(() => { - requestUserWithPerms(okapi.url, store, tenant, token) + requestUserWithPerms(store.getState().okapi.url, store, tenant, token) .then(() => { if (store.getState()?.okapi?.authFailure) { return Promise.reject(new Error('SSO Failed')); diff --git a/src/components/SSOLanding/useSSOSession.test.js b/src/components/SSOLanding/useSSOSession.test.js index 06eac936e..07e516ec6 100644 --- a/src/components/SSOLanding/useSSOSession.test.js +++ b/src/components/SSOLanding/useSSOSession.test.js @@ -4,7 +4,7 @@ import { useDispatch, useStore } from 'react-redux'; import { useLocation } from 'react-router-dom'; import { useCookies } from 'react-cookie'; -import { config, okapi } from 'stripes-config'; +import { config } from 'stripes-config'; import { defaultErrors } from '../../constants'; import { setAuthError } from '../../okapiActions'; @@ -30,13 +30,11 @@ jest.mock('stripes-config', () => ({ config: { useSecureTokens: true, }, - okapi: { - url: 'okapiUrl', - tenant: 'okapiTenant' - } }), { virtual: true }); +jest.mock(''); + jest.mock('../../loginServices', () => ({ requestUserWithPerms: jest.fn() })); @@ -75,7 +73,7 @@ describe('SSOLanding', () => { renderHook(() => useSSOSession()); - expect(requestUserWithPerms).toHaveBeenCalledWith(okapi.url, store, okapi.tenant, ssoTokenValue); + expect(requestUserWithPerms).toHaveBeenCalledWith(store.getState().okapi.url, store, store.getState().okapi.tenant, ssoTokenValue); }); it('should request user session when RTR is disabled with token from cookies', () => { @@ -86,7 +84,7 @@ describe('SSOLanding', () => { renderHook(() => useSSOSession()); - expect(requestUserWithPerms).toHaveBeenCalledWith(okapi.url, store, okapi.tenant, ssoTokenValue); + expect(requestUserWithPerms).toHaveBeenCalledWith(store.getState().okapi.url, store, 'okapiTenant', ssoTokenValue); }); it('should request user session when RTR is disabled and right tenant from ssoToken', () => { @@ -99,7 +97,7 @@ describe('SSOLanding', () => { renderHook(() => useSSOSession()); - expect(requestUserWithPerms).toHaveBeenCalledWith(okapi.url, store, tokenTenant, ssoTokenValue); + expect(requestUserWithPerms).toHaveBeenCalledWith(store.getState().okapi.url, store, tokenTenant, ssoTokenValue); }); it('should request user session when RTR is enabled and right tenant from query params', () => { @@ -111,7 +109,7 @@ describe('SSOLanding', () => { renderHook(() => useSSOSession()); - expect(requestUserWithPerms).toHaveBeenCalledWith(okapi.url, store, queryTenant, undefined); + expect(requestUserWithPerms).toHaveBeenCalledWith(store.getState().okapi.url, store, queryTenant, undefined); }); it('should display error when session request failed', async () => { diff --git a/src/loginServices.js b/src/loginServices.js index 71670c706..827d64527 100644 --- a/src/loginServices.js +++ b/src/loginServices.js @@ -1,5 +1,5 @@ import localforage from 'localforage'; -import { config, okapi, translations } from 'stripes-config'; +import { config, translations } from 'stripes-config'; import rtlDetect from 'rtl-detect'; import moment from 'moment'; import { loadDayJSLocale } from '@folio/stripes-components'; @@ -400,11 +400,12 @@ function loadResources(store, tenant, userId) { // in mod-configuration so we can only retrieve them if the user has // read-permission for configuration entries. if (canReadConfig(store)) { + const okapiUrl = store.getState()?.okapi?.url; promises = [ - getLocale(okapi.url, store, tenant), - getUserLocale(okapi.url, store, tenant, userId), - getPlugins(okapi.url, store, tenant), - getBindings(okapi.url, store, tenant), + getLocale(okapiUrl, store, tenant), + getUserLocale(okapiUrl, store, tenant, userId), + getPlugins(okapiUrl, store, tenant), + getBindings(okapiUrl, store, tenant), ]; } @@ -783,7 +784,7 @@ export function processOkapiSession(store, tenant, resp, ssoToken) { */ export function validateUser(okapiUrl, store, tenant, session) { const { token, tenant: sessionTenant = tenant } = session; - const usersPath = okapi.authnUrl ? 'users-keycloak' : 'bl-users'; + const usersPath = store.getState()?.okapi?.authnUrl ? 'users-keycloak' : 'bl-users'; return fetch(`${okapiUrl}/${usersPath}/_self?expandPermissions=true`, { headers: getHeaders(sessionTenant, token), credentials: 'include', @@ -869,7 +870,7 @@ export function checkOkapiSession(okapiUrl, store, tenant) { * requestLogin * authenticate with a username and password. return a promise that posts the values * and then processes the result to begin a session. - * @param {object} okapi object from stripes.config.js + * @param {string} okapiUrl * @param {redux store} store * @param {string} tenant * @param {object} data @@ -878,14 +879,13 @@ export function checkOkapiSession(okapiUrl, store, tenant) { */ export function requestLogin(okapiUrl, store, tenant, data) { // got Keycloak? - if (okapi.authnUrl) { - return fetch(okapi.authnUrl, { + if (store.getState().okapi.authnUrl) { + return fetch(store.getState().okapi.authnUrl, { method: 'POST', body: new URLSearchParams({ ...data, 'grant_type': 'password', - 'client_id': okapi.clientId, - 'client_secret': okapi.clientSecret, + 'client_id': store.getState()?.okapi?.clientId, }) }) .then(resp => processOkapiSession(store, tenant, resp)); @@ -910,11 +910,12 @@ export function requestLogin(okapiUrl, store, tenant, data) { * @param {string} tenant * @param {string} token * @param {boolean} rtrIgnore + * @param {boolean} isKeycloak * * @returns {Promise} Promise resolving to the response of the request */ -function fetchUserWithPerms(okapiUrl, tenant, token, rtrIgnore = false) { - const usersPath = okapi.authnUrl ? 'users-keycloak' : 'bl-users'; +function fetchUserWithPerms(okapiUrl, tenant, token, rtrIgnore = false, isKeycloak) { + const usersPath = isKeycloak ? 'users-keycloak' : 'bl-users'; return fetch( `${okapiUrl}/${usersPath}/_self?expandPermissions=true&fullPermissions=true`, { @@ -934,7 +935,8 @@ function fetchUserWithPerms(okapiUrl, tenant, token, rtrIgnore = false) { * @returns {Promise} Promise resolving to the response-body (JSON) of the request */ export function requestUserWithPerms(okapiUrl, store, tenant, token) { - return fetchUserWithPerms(okapiUrl, tenant, token, !token) + const isKeycloak = store.getState().okapi?.authnUrl; + return fetchUserWithPerms(okapiUrl, tenant, token, !token, isKeycloak) .then((resp) => { if (resp.ok) { return processOkapiSession(store, tenant, resp, token); diff --git a/src/loginServices.test.js b/src/loginServices.test.js index da048d851..a55bf9bb5 100644 --- a/src/loginServices.test.js +++ b/src/loginServices.test.js @@ -62,9 +62,6 @@ jest.mock('stripes-config', () => ({ remus: { name: 'remus', clientId: 'remus-application' }, } }, - okapi: { - authnUrl: 'https://authn.url', - }, translations: {} }));