=> {
- const user = isITLessCognito ? await createUser() : buildUser(token);
-
- const pathName = getWindow().location.pathname.split('/');
- pathName.shift();
- if (pathName[0] === 'beta') {
- pathName.shift();
- }
- if (pathName?.[1] === 'subscriptions' || pathName?.[1] === 'cost-management') {
- pathName.shift();
- }
-
- if (user) {
- log(`Account Number: ${user.identity.account_number}`);
- let data: {
- [key: string]: {
- is_entitled: boolean;
- is_trial: boolean;
- };
- } = {};
- // let cogToken;
- if (isITLessCognito) {
- // cogToken = await getTokenWithAuthorizationCode();
- }
- try {
- if (user.identity.org_id) {
- data = isITLessCognito
- ? ((await serviceAPI.servicesGet()) as unknown as typeof data)
- : ((await serviceAPI.servicesGet()) as unknown as typeof data);
- } else {
- console.log('Cannot call entitlements API, no account number');
- }
- } catch {
- // let's swallow error from services API
- }
-
- // NOTE: Openshift supports Users with Account Number of -1
- // thus we need to bypass here
- // call entitlements on / /beta /openshift or /beta/openshift,
- // but swallow error
- //
- // Landing Page *does* support accounts with -1
- // it has to
- if (pageAllowsUnentitled()) {
- return {
- ...user,
- entitlements: data,
- };
- }
-
- // Important this has to come after the above -1 allow checks
- // Otherwise we get bounced on those paths
- //
- // It also needs to not go int he servicesApi call
- // because 3scale 403s if the Account number is -1
- //
- // we "force" a bounce here because the entitlements API
- // was never called
- if (!isValidAccountNumber(user.identity.account_number)) {
- tryBounceIfUnentitled(true, pathName[0]);
- // always return user regardless of the entitlements result
- // required for insights accounts with invalid account number
- return {
- ...user,
- entitlements: data,
- };
- }
-
- tryBounceIfUnentitled(data as unknown as { [key: string]: SSOServiceDetails }, pathName[0]);
-
- return {
- ...user,
- entitlements: data,
- };
- } else {
- log('User not ready');
- }
-};
diff --git a/src/jwt/user.unit.test.js b/src/jwt/user.unit.test.js
deleted file mode 100644
index b1d4f427e..000000000
--- a/src/jwt/user.unit.test.js
+++ /dev/null
@@ -1,121 +0,0 @@
-/* eslint-disable @typescript-eslint/no-var-requires */
-jest.mock('./entitlements');
-
-const token = require('../../testdata/token.json');
-const userOutput = require('../../testdata/user.json');
-const user = require('./user');
-const { setAnsibleTrialFlag, ANSIBLE_TRIAL_FLAG } = require('../utils/isAnsibleTrialFlagActive');
-const replaceMock = jest.fn();
-const chromeHistory = require('../utils/chromeHistory');
-
-jest.mock('../utils/chromeHistory', () => {
- return {
- __esModule: true,
- default: {
- replace: jest.fn(),
- },
- };
-});
-
-describe('User', () => {
- const { location } = window;
-
- beforeAll(() => {
- delete window.location;
- window.location = {
- pathname: '/insights/foo',
- replace: replaceMock,
- };
- });
-
- afterAll(() => {
- window.location = location;
- });
-
- describe('buildUser', () => {
- test('transforms a token into a User object', () => {
- expect(user.buildUser(token)).toEqual(userOutput);
- });
- });
-
- /* eslint-disable camelcase */
- describe('tryBounceIfUnentitled', () => {
- const ents = {
- insights: { is_entitled: false },
- openshift: { is_entitled: false },
- cost_management: { is_entitled: false },
- };
-
- beforeEach(() => {
- replaceMock.mockReset();
- });
-
- test('should *not* bounce if the section is unkown', () => {
- ents.insights.is_entitled = false;
- user.tryBounceIfUnentitled(ents, 'apps');
- user.tryBounceIfUnentitled(ents, 'foo');
- user.tryBounceIfUnentitled(ents, 'test');
- expect(replaceMock).not.toBeCalled();
- });
-
- test('should bounce if unentitled', () => {
- const historySpy = jest.spyOn(chromeHistory.default, 'replace');
- user.tryBounceIfUnentitled(ents, 'insights');
- expect(historySpy).lastCalledWith({ pathname: '/', search: '?not_entitled=insights' });
-
- user.tryBounceIfUnentitled(ents, 'cost-management');
- expect(historySpy).lastCalledWith({ pathname: '/', search: '?not_entitled=cost_management' });
-
- user.tryBounceIfUnentitled(ents, 'ansible');
- expect(historySpy).lastCalledWith({ pathname: '/ansible/ansible-dashboard/trial', search: '' });
- historySpy.mockRestore();
- });
-
- test('should properly bounce if unentitled user with ansible trial locastorage flags', () => {
- const historySpy = jest.spyOn(chromeHistory.default, 'replace');
- jest.useFakeTimers();
- // enable ansible trial flag
- setAnsibleTrialFlag(Date.now());
- // advance time by one minute. user should not be bounced
- jest.advanceTimersByTime(1 * 60 * 1000);
- user.tryBounceIfUnentitled(ents, 'ansible');
- expect(historySpy).not.toBeCalled();
-
- // advace time by additional 10 minutes. user should be bounced to /trial/expired
- jest.advanceTimersByTime(10 * 60 * 1000);
- user.tryBounceIfUnentitled(ents, 'ansible');
- expect(historySpy).toBeCalledTimes(1);
- expect(historySpy).toHaveBeenLastCalledWith({ pathname: '/ansible/ansible-dashboard/trial/expired', search: '' });
- historySpy.mockClear();
-
- // should not be bounced at all if entitled to ansible
- user.tryBounceIfUnentitled(
- {
- ansible: {
- is_entitled: true,
- },
- },
- 'ansible'
- );
- expect(historySpy).not.toBeCalled();
-
- // clear the ansible trial flag
- localStorage.removeItem(ANSIBLE_TRIAL_FLAG);
- });
-
- test('should *not* bounce if entitled', () => {
- ents.insights.is_entitled = true;
- user.tryBounceIfUnentitled(ents, 'insights');
- expect(replaceMock).not.toBeCalled();
- });
- });
- /* eslint-enable camelcase */
-
- describe('default', () => {
- test('appends the entitlements data onto the user object', async () => {
- const o = await user.default(token);
- expect(o).toHaveProperty('entitlements', { foo: 'bar' });
- expect(o).toHaveProperty('identity');
- });
- });
-});
diff --git a/src/layouts/DefaultLayout.tsx b/src/layouts/DefaultLayout.tsx
index 4995c33c0..bb1f063fd 100644
--- a/src/layouts/DefaultLayout.tsx
+++ b/src/layouts/DefaultLayout.tsx
@@ -1,4 +1,4 @@
-import React, { memo, useEffect, useRef, useState } from 'react';
+import React, { memo, useContext, useEffect, useRef, useState } from 'react';
import classnames from 'classnames';
import { useSelector } from 'react-redux';
import GlobalFilter from '../components/GlobalFilter/GlobalFilter';
@@ -27,6 +27,7 @@ import useNavigation from '../utils/useNavigation';
import { NavigationProps } from '../components/Navigation';
import { getUrl } from '../hooks/useBundle';
import { useFlag } from '@unleash/proxy-client-react';
+import ChromeAuthContext from '../auth/ChromeAuthContext';
type ShieldedRootProps = {
hideNav?: boolean;
@@ -172,7 +173,8 @@ export type RootAppProps = {
const DefaultLayoutRoot = ({ Sidebar, Footer }: RootAppProps) => {
const ouiaTags = useOuiaTags();
const initialized = useScalprum(({ initialized }) => initialized);
- const hideNav = useSelector(({ chrome: { user } }: ReduxState) => !user || !Sidebar);
+ const { user } = useContext(ChromeAuthContext);
+ const hideNav = !user || !Sidebar;
return (
diff --git a/src/redux/action-types.ts b/src/redux/action-types.ts
index 16b3f49fb..08c20fb32 100644
--- a/src/redux/action-types.ts
+++ b/src/redux/action-types.ts
@@ -21,7 +21,6 @@ export const LOAD_NAVIGATION_LANDING_PAGE = '@@chrome/load-navigation-landing-pa
export const LOAD_LEFT_NAVIGATION_SEGMENT = '@@chrome/load-navigation-segment';
export const LOAD_MODULES_SCHEMA = '@@chrome/load-modules-schema';
-export const CHANGE_ACTIVE_MODULE = '@@chrome/change-active-module';
export const SET_PENDO_FEEDBACK_FLAG = '@@chrome/set-pendo-feedback-flag';
export const TOGGLE_FEEDBACK_MODAL = '@@chrome/toggle-feedback-modal';
export const TOGGLE_DEBUGGER_MODAL = '@@chrome/toggle-debugger-modal';
diff --git a/src/redux/actions.ts b/src/redux/actions.ts
index 351283917..f3b580d50 100644
--- a/src/redux/actions.ts
+++ b/src/redux/actions.ts
@@ -119,11 +119,6 @@ export const loadModulesSchema = (schema: { [key: string]: ChromeModule }) => ({
},
});
-export const changeActiveModule = (module: string) => ({
- type: actionTypes.CHANGE_ACTIVE_MODULE,
- payload: module,
-});
-
/**
* @deprecated
*/
diff --git a/src/redux/chromeReducers.ts b/src/redux/chromeReducers.ts
index d01f3e627..1ef33d061 100644
--- a/src/redux/chromeReducers.ts
+++ b/src/redux/chromeReducers.ts
@@ -25,7 +25,6 @@ export function loginReducer(state: ChromeState, { payload }: { payload: ChromeU
return {
...state,
missingIDP,
- user: payload,
};
}
@@ -155,14 +154,6 @@ export function loadModulesSchemaReducer(
};
}
-export function changeActiveModuleReducer(state: ChromeState, { payload }: { payload: string }): ChromeState {
- return {
- ...state,
- activeModule: payload,
- appId: payload,
- };
-}
-
export function setPendoFeedbackFlag(
state: ChromeState,
{
diff --git a/src/redux/index.ts b/src/redux/index.ts
index 402987f0e..e7aff72f6 100644
--- a/src/redux/index.ts
+++ b/src/redux/index.ts
@@ -4,7 +4,6 @@ import {
accessRequestsNotificationsReducer,
addQuickstartstoApp,
appNavClick,
- changeActiveModuleReducer,
clearQuickstartsReducer,
contextSwitcherBannerReducer,
disableQuickstartsReducer,
@@ -48,7 +47,6 @@ import {
import {
ADD_QUICKSTARTS_TO_APP,
APP_NAV_CLICK,
- CHANGE_ACTIVE_MODULE,
CHROME_GET_ALL_SIDS,
CHROME_GET_ALL_TAGS,
CHROME_GET_ALL_WORKLOADS,
@@ -97,7 +95,6 @@ const reducers = {
[LOAD_NAVIGATION_LANDING_PAGE]: loadNavigationLandingPageReducer,
[LOAD_LEFT_NAVIGATION_SEGMENT]: loadNavigationSegmentReducer,
[LOAD_MODULES_SCHEMA]: loadModulesSchemaReducer,
- [CHANGE_ACTIVE_MODULE]: changeActiveModuleReducer,
[SET_PENDO_FEEDBACK_FLAG]: setPendoFeedbackFlag,
[TOGGLE_FEEDBACK_MODAL]: toggleFeedbackModal,
[TOGGLE_DEBUGGER_MODAL]: toggleDebuggerModal,
diff --git a/src/redux/store.d.ts b/src/redux/store.d.ts
index beff48101..3ae8c581b 100644
--- a/src/redux/store.d.ts
+++ b/src/redux/store.d.ts
@@ -1,5 +1,4 @@
import { QuickStart } from '@patternfly/quickstarts';
-import { ChromeUser } from '@redhat-cloud-services/types';
import { ChromeModule, FlagTagsFilter, NavItem, Navigation, RouteDefinition } from '../@types/types';
import { ThreeScaleError } from '../utils/responseInterceptors';
@@ -40,15 +39,8 @@ export type NotificationsPayload = {
export type ChromeState = {
contextSwitcherOpen: boolean;
activeApp?: string;
- activeModule?: string;
activeProduct?: string;
- /**
- * @deprecated
- * App id is replaced by active module. It is still required until we completely remove usage of main.yml
- */
- appId?: string;
missingIDP?: boolean;
- user?: ChromeUser;
pageAction?: string;
pageObjectId?: string;
modules?: { [key: string]: ChromeModule };
diff --git a/src/state/atoms.ts b/src/state/atoms.ts
new file mode 100644
index 000000000..873317f5b
--- /dev/null
+++ b/src/state/atoms.ts
@@ -0,0 +1,4 @@
+import { atom } from 'jotai';
+// setup initial chrome atoms
+export const contextSwitcherOpenAtom = atom(false);
+export const activeModuleAtom = atom(undefined);
diff --git a/src/state/chromeStore.ts b/src/state/chromeStore.ts
new file mode 100644
index 000000000..d0e94f57a
--- /dev/null
+++ b/src/state/chromeStore.ts
@@ -0,0 +1,15 @@
+import { createStore } from 'jotai';
+import { activeModuleAtom, contextSwitcherOpenAtom } from './atoms';
+
+const chromeStore = createStore();
+
+// setup initial chrome store state
+chromeStore.set(contextSwitcherOpenAtom, false);
+chromeStore.set(activeModuleAtom, undefined);
+
+// globally handle subscription to activeModuleAtom
+chromeStore.sub(activeModuleAtom, () => {
+ // console.log('activeModule in store', chromeStore.get(activeModuleAtom));
+});
+
+export default chromeStore;
diff --git a/src/utils/common.ts b/src/utils/common.ts
index 8334a0d1b..aae68ae8f 100644
--- a/src/utils/common.ts
+++ b/src/utils/common.ts
@@ -39,7 +39,7 @@ export const DEFAULT_SSO_ROUTES = {
},
frh: {
url: ['console.openshiftusgov.com'],
- sso: 'https://ocm-ra-prod-domain.auth-fips.us-gov-west-1.amazoncognito.com/login',
+ sso: 'https://sso.openshiftusgov.com',
portal: 'https://console.openshiftusgov.com',
},
ephem: {
@@ -215,11 +215,7 @@ export function ITLess() {
}
export function ITLessCognito() {
- return getEnv() === 'frh';
-}
-
-export function ITLessKeycloak() {
- return getEnv() === 'ephem' || getEnv() === 'int' || getEnv() === 'scr' || getEnv() === 'frhStage';
+ return getEnv() === 'ephem';
}
export function updateDocumentTitle(title?: string, noSuffix = false) {
diff --git a/src/utils/consts.ts b/src/utils/consts.ts
index e5c66d79e..4b13bd206 100644
--- a/src/utils/consts.ts
+++ b/src/utils/consts.ts
@@ -1,4 +1,4 @@
-import { ITLess, ITLessKeycloak } from './common';
+import { ITLess } from './common';
import { AppNavigationCB, ChromeAuthOptions, GenericCB, NavDOMEvent } from '../@types/types';
import { Listener } from '@redhat-cloud-services/frontend-components-utilities/MiddlewareListener';
import { APP_NAV_CLICK, GLOBAL_FILTER_UPDATE } from '../redux/action-types';
@@ -65,7 +65,7 @@ export const activationRequestURLs = [
// Global Defaults
export const defaultAuthOptions: ChromeAuthOptions = {
realm: 'redhat-external',
- clientId: ITLessKeycloak() ? 'console-dot' : 'cloud-services',
+ clientId: ITLess() ? 'console-dot' : 'cloud-services',
cookieName: 'cs_jwt',
};
diff --git a/src/utils/createCase.ts b/src/utils/createCase.ts
index a4016a50f..d84293cd0 100644
--- a/src/utils/createCase.ts
+++ b/src/utils/createCase.ts
@@ -1,14 +1,14 @@
import * as Sentry from '@sentry/react';
-import logger from '../jwt/logger';
+import logger from '../auth/logger';
import URI from 'urijs';
const log = logger('createCase.js');
import { getEnvDetails, isBeta, isProd } from './common';
import { HYDRA_ENDPOINT } from './consts';
-import { spinUpStore } from '../redux/redux-config';
import { ChromeUser } from '@redhat-cloud-services/types';
-import { LibJWT } from '../auth';
import { getUrl } from '../hooks/useBundle';
+import chromeStore from '../state/chromeStore';
+import { activeModuleAtom } from '../state/atoms';
// Lit of products that are bundles
const BUNDLE_PRODUCTS = [
@@ -63,15 +63,14 @@ async function getAppInfo(activeModule: string) {
}
async function getProductData() {
- const { store } = spinUpStore();
- const activeModule = store.getState().chrome.activeModule || '';
- const appData = await getAppInfo(activeModule);
+ const activeModule = chromeStore.get(activeModuleAtom);
+ const appData = await getAppInfo(activeModule ?? '');
return appData;
}
export async function createSupportCase(
userInfo: ChromeUser['identity'],
- libjwt: LibJWT,
+ token: string,
fields?: {
caseFields: Record;
}
@@ -85,7 +84,6 @@ export async function createSupportCase(
log('Creating a support case');
- const token = await libjwt.initPromise.then(() => libjwt.jwt.getUserInfo().then(() => libjwt.jwt.getEncodedToken()));
fetch(caseUrl, {
method: 'POST',
headers: {
diff --git a/src/utils/debugFunctions.ts b/src/utils/debugFunctions.ts
index 9021e2160..cd9b8c890 100644
--- a/src/utils/debugFunctions.ts
+++ b/src/utils/debugFunctions.ts
@@ -8,7 +8,7 @@ const debugFunctions = {
remediationsDebug: () => functionBuilder('remediations:debug', true),
invTags: () => functionBuilder('rhcs-tags', true),
shortSession: () => functionBuilder('chrome:jwt:shortSession', true),
- jwtDebug: () => functionBuilder('chrome:jwt:debug', true),
+ jwtDebug: () => functionBuilder('chrome:auth:debug', true),
reduxDebug: () => functionBuilder('chrome:redux:debug', true),
forcePendo: () => functionBuilder('forcePendo', true),
disableSegment: () => functionBuilder('chrome:segment:disable', true),
diff --git a/src/utils/iqeEnablement.ts b/src/utils/iqeEnablement.ts
index f8d4adf27..74392d51c 100644
--- a/src/utils/iqeEnablement.ts
+++ b/src/utils/iqeEnablement.ts
@@ -1,23 +1,24 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable prefer-rest-params */
import type { Store } from 'redux';
-import { LibJWT, crossAccountBouncer } from '../auth';
import { setGatewayError } from '../redux/actions';
import { get3scaleError } from './responseInterceptors';
+import crossAccountBouncer from '../auth/crossAccountBouncer';
// TODO: Refactor this file to use modern JS
let xhrResults: XMLHttpRequest[] = [];
let fetchResults: Record = {};
-const DENINED_CROSS_CHECK = 'Access denied from RBAC on cross-access check';
+const DENIED_CROSS_CHECK = 'Access denied from RBAC on cross-access check';
+const AUTH_ALLOWED_ORIGINS = [location.origin, 'https://api.openshift.com', 'https://api.stage.openshift.com'];
const checkOrigin = (path: URL | Request | string = '') => {
- if (path.constructor.name === 'URL') {
- return (path as URL).origin === location.origin;
- } else if (path.constructor.name === 'Request') {
- return (path as Request).url.includes(location.origin);
- } else if (path.constructor.name === 'String') {
- return (path as string).includes(location.origin) || !(path as string).startsWith('http');
+ if (path instanceof URL) {
+ return AUTH_ALLOWED_ORIGINS.includes(path.origin);
+ } else if (path instanceof Request) {
+ return AUTH_ALLOWED_ORIGINS.some((origin) => path.url.includes(origin));
+ } else if (typeof path === 'string') {
+ return AUTH_ALLOWED_ORIGINS.some((origin) => path.includes(origin)) || !path.startsWith('http');
}
return true;
@@ -38,7 +39,7 @@ const spreadAdditionalHeaders = (options: RequestInit | undefined) => {
return additionalHeaders;
};
-function init(store: Store, libJwt?: () => LibJWT | undefined) {
+export function init(store: Store, token: string) {
const open = window.XMLHttpRequest.prototype.open;
const send = window.XMLHttpRequest.prototype.send;
const setRequestHeader = window.XMLHttpRequest.prototype.setRequestHeader;
@@ -74,10 +75,10 @@ function init(store: Store, libJwt?: () => LibJWT | undefined) {
// must use function here because arrows dont "this" like functions
window.XMLHttpRequest.prototype.send = function sendReplacement() {
- if (checkOrigin((this as XMLHttpRequest & { _url: string })._url) && libJwt?.()?.jwt.isAuthenticated()) {
+ if (checkOrigin((this as XMLHttpRequest & { _url: string })._url)) {
if (!authRequests.has((this as XMLHttpRequest & { _url: string })._url)) {
// Send Auth header, it will be changed to Authorization later down the line
- this.setRequestHeader('Auth', `Bearer ${libJwt?.()?.jwt.getEncodedToken()}`);
+ this.setRequestHeader('Auth', `Bearer ${token}`);
}
}
// eslint-disable-line func-names
@@ -87,7 +88,7 @@ function init(store: Store, libJwt?: () => LibJWT | undefined) {
this.onload = function () {
if (this.status >= 400) {
const gatewayError = get3scaleError(this.response);
- if (this.status === 403 && this.responseText.includes(DENINED_CROSS_CHECK)) {
+ if (this.status === 403 && this.responseText.includes(DENIED_CROSS_CHECK)) {
crossAccountBouncer();
// check for 3scale error
} else if (gatewayError) {
@@ -107,8 +108,8 @@ function init(store: Store, libJwt?: () => LibJWT | undefined) {
const tid = Math.random().toString(36);
const request: Request = new Request(input, init);
- if (checkOrigin(input) && libJwt?.()?.jwt.isAuthenticated() && !request.headers.has('Authorization')) {
- request.headers.append('Authorization', `Bearer ${libJwt?.()?.jwt.getEncodedToken()}`);
+ if (checkOrigin(input) && !request.headers.has('Authorization')) {
+ request.headers.append('Authorization', `Bearer ${token}`);
}
const prom = oldFetch.apply(this, [request, ...rest]);
diff --git a/src/utils/isAnsibleTrialFlagActive.ts b/src/utils/isAnsibleTrialFlagActive.ts
index c6f5c8c7a..e7c6043ba 100644
--- a/src/utils/isAnsibleTrialFlagActive.ts
+++ b/src/utils/isAnsibleTrialFlagActive.ts
@@ -1,4 +1,4 @@
-import logger from '../jwt/logger';
+import logger from '../auth/logger';
export const ANSIBLE_TRIAL_FLAG = 'chrome.ansible.trial';
const TRIAL_DURATION = 10 * 60 * 1000; // 10 minutes
diff --git a/src/utils/useAccessRequestNotifier.ts b/src/utils/useAccessRequestNotifier.ts
index b8197c158..d26020630 100644
--- a/src/utils/useAccessRequestNotifier.ts
+++ b/src/utils/useAccessRequestNotifier.ts
@@ -1,12 +1,13 @@
import axios from 'axios';
-import { useEffect, useRef } from 'react';
+import { useContext, useEffect, useRef } from 'react';
import { batch, useDispatch, useSelector } from 'react-redux';
import { REQUESTS_COUNT, REQUESTS_DATA } from './consts';
import { markAccessRequestNotification, updateAccessRequestsNotifications } from '../redux/actions';
import { ReduxState } from '../redux/store';
+import ChromeAuthContext from '../auth/ChromeAuthContext';
const useAccessRequestNotifier = (): [ReduxState['chrome']['accessRequests'], (id: string | number) => void] => {
- const user = useSelector(({ chrome }: ReduxState) => chrome?.user);
+ const { user } = useContext(ChromeAuthContext);
const isMounted = useRef(false);
const state = useSelector(({ chrome: { accessRequests } }: ReduxState) => accessRequests);
const dispatch = useDispatch();