Untitled

mail@pastecode.io avatar
unknown
plain_text
a year ago
14 kB
1
Indexable
Never
import React, { Suspense, useEffect, useState } from 'react';
import './App.scss';
import { Route, Switch, Redirect, useLocation, useHistory } from 'react-router-dom';
import Policies from './access-policies/components/policies/Policies';
import { ThemeProvider, Theme, StyledEngineProvider } from '@mui/material/styles';
import { scaTheme } from './scaTheme';
import Wizard, { WizardMode } from './create-edit-policy/wizard/Wizard';
import { INavBarItem, updateSideNavigation, updateShellURL, GeneralMessageEnum } from '@cyberark/shell-sdk';
import { isInIframe } from './utils/shellUtils';
import IntroPage from './introducation-page/introPage';
import './interceptors/CsrfInterceptor';
import './interceptors/RefreshTokenInterceptor';
import { runMocks } from './mocks/mocker';
import { useDispatch, useSelector } from 'react-redux';
import { MessagesState } from './global-state/reducers/ShowMessagesReducer';
import { ConfigService } from './access-policies/services/ConfigService';
import {
  setSocketConnectionId,
  updateAccessMap,
  updateApplicationConfig,
  updateCemOnboarding,
  updateCloudRequestObject,
  updateOnboardingDone,
  updatePoliciesTable,
} from './global-state/Actions';
import CircularProgress from '@mui/material/CircularProgress';
import { Box } from '@mui/material';
import { hideIntroduction, onHideIntroductionButton } from '@cyberark/shell-sdk/dist/shell-sdk';
import ErrorDialog from './error-handling/error-dialog/ErrorDialog';
import Roles from './cloud-roles/components/CloudRoles';
import ServiceUnavalable from './error-handling/service-unavalable/serviceUnavalable';
import { ErrorBoundary } from 'react-error-boundary';
import { shouldInitShellSDK } from './index';
import RequestForm from './request-form/RequestForm';
import { QueryClient, QueryClientProvider } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools';
import { PermissionService } from './shared/PermissionService';
import { PoliciesService } from './access-policies/services/PoliciesService';
import { CemAzureOnBoardingData } from './onboarding/azure-onboarding/CemAzureOnBoardingData';
import { CemGcpOnBoardingData } from './onboarding/gcp-onboarding/CemGcpOnBoardingData';
import AzureOnboarding from './onboarding/azure-onboarding/AzureOnboarding';
import SelfService from './self-service/SelfService';
import GcpOnboarding from './onboarding/gcp-onboarding/GcpOnboarding';
import { ApplicationConfig } from './global-state/reducers/ApplicationConfigReducer';
import AccessPolicies from './access-policies/AccessPolicies';
import { SocketService } from 'src/cloud-roles/services/SocketService';
import { UserService } from './access-policies/services/UserService';
import { setUserPermissionsAction } from './global-state/actions/user.actions';
import ProtectedRoute from './shared/hoc/ProtectedRoute';
declare module '@mui/styles/defaultTheme' {
  // eslint-disable-next-line @typescript-eslint/no-empty-interface
  interface DefaultTheme extends Theme {
    config?: string;
  }
}

export const navBarItems = [
  {
    label: `Introduction`,
    url: 'introduction',
    iconClass: 'cyb-ic-welcome',
    isRoot: true,
  } as INavBarItem,
  {
    label: `Policies`,
    url: 'policies',
    iconClass: 'cyb-ic-policy-list',
    isRoot: true,
  } as INavBarItem,
  {
    label: `Settings`,
    url: 'settings',
    iconClass: 'cyb-ic-settings',
    isRoot: true,
    shouldHide: true,
  } as INavBarItem,
  {
    label: `gcp_onboarding`,
    url: 'gcp_onboarding',
    iconClass: 'cyb-ic-recuring-access',
    isRoot: true,
    shouldHide: true,
  } as INavBarItem,
];

export const navBarItemsWithoutIntro = [
  {
    label: `Policies`,
    url: 'policies',
    iconClass: 'cyb-ic-policy-list',
    isRoot: true,
  } as INavBarItem,
  {
    label: `Settings`,
    url: 'settings',
    iconClass: 'cyb-ic-settings',
    isRoot: true,
    shouldHide: true,
  } as INavBarItem,
  {
    label: `gcp_onboarding`,
    url: 'gcp_onboarding',
    iconClass: 'cyb-ic-recuring-access',
    isRoot: true,
    shouldHide: true,
  } as INavBarItem,
];

const socketService = new SocketService();
const queryClient = new QueryClient();
const userService = new UserService();
function App() {
  const location = useLocation();
  if (process.env.REACT_APP_MOCK === 'mock') {
    runMocks();
  }
  const appConfig: ApplicationConfig = useSelector<ApplicationConfig>(
    (state: any) => state.appConfig.applicationConfig
  ) as ApplicationConfig;
  const webSocketFlagEnabled = appConfig.wizardConfig.enableWebSocket;
  const permissionService = new PermissionService();
  const errorMessage = useSelector<MessagesState>((state: any) => state.messages) as MessagesState;
  const dispatch = useDispatch();
  const [loading, setLoading] = React.useState(true);
  const [fetchUserPermissionsLoading, setFetchUserPermissionsLoading] = useState(true);
  const history = useHistory();
  const [defaultRoute, setDefaultRoute] = useState<string>('/Introduction');
  const [showHide, setShowHide] = useState(true);
  let socket: WebSocket;
  let pingInterval: any;

  useEffect(() => {
    getUserPermissions();
  }, []);
  const getUserPermissions = async () => {
    try {
      console.log('getUserPermissions');
      const res = await userService.getUserPermissions();
      console.log({ res });
      dispatch(setUserPermissionsAction(res.data.actions));
      setFetchUserPermissionsLoading(false);
    } catch (e) {
      console.error(e);
      setFetchUserPermissionsLoading(false);
    }
  };
  const updateNavBarItems = (items: INavBarItem[], appConfigData: ApplicationConfig) => {
    return items.map((item) => {
      if (item.url === 'settings') {
        item.shouldHide = !appConfigData.navBarItemsShow.selfService;
      }
      return item;
    });
  };

  const startPingSending = () => {
    pingInterval = setInterval(() => {
      if (!socket) {
        return;
      }
      if (socket.readyState === socket.OPEN) {
        socket.send('heartbeat');
      }
    }, 1000 * 30);
  };

  window.addEventListener(
    'message',
    (e) => {
      console.log('event listener was invoked');
      try {
        if (e.origin !== window.location.href) {
          const data = e.data;
          if (data.SCARequestPayload) {
            const esacped = data.SCARequestPayload.replace(/&quot;/g, '"');
            console.log('esacped' + esacped);
            const reqObj = JSON.parse(esacped);
            dispatch(updateCloudRequestObject(reqObj));
          }
          if (e.data.type === GeneralMessageEnum.GENERAL) {
            const cemData = e.data.data;
            switch (cemData.type) {
              case 'CEM_ONBORADING_DATA':
                if (window.location.hash.includes('gcp_onboarding')) {
                  dispatch(updateCemOnboarding(new CemGcpOnBoardingData(cemData)));
                  break;
                }
                if (window.location.hash.includes('azure_onboarding')) {
                  dispatch(updateCemOnboarding(new CemAzureOnBoardingData(cemData)));
                  break;
                }
                break;
              case 'CEM_ONBOARIDNG_PROCESS_DONE':
                dispatch(updateOnboardingDone({ onboarding: cemData }));
                break;
              case 'ACCESS_MAP_LOADED':
                dispatch(updateAccessMap({ accessMap: true }));
                break;
            }
          }
        }
      } catch (ex) {
        console.error('error from identity listener ' + ex);
      }
    },
    false
  );

  const connectWebSocket = (url: string) => {
    socket = new WebSocket(url);
    socket.onopen = (event: any) => {
      console.log('Websocket is connected');
      startPingSending();
    };

    socket.onerror = (error: any) => {
      console.error('Socket encountered error: ', error.message, 'Closing socket');
      socket.close();
    };

    socket.onmessage = (event: any) => {
      const dataFromServer = JSON.parse(event.data.toString());
      if (dataFromServer.policy_id) {
        console.log('Message received:', event.data);
        dispatch(updatePoliciesTable(dataFromServer));
      }
      if (dataFromServer.connection_id) {
        console.log('Message received connection id:', event.data);
        dispatch(setSocketConnectionId(dataFromServer));
      }
    };

    socket.onclose = (event: any) => {
      console.error('Socket is closed. Reconnect will be attempted in 1 second.', event.reason);
      if (pingInterval) {
        clearInterval(pingInterval);
      }

      setTimeout(() => {
        initWebsocket();
      }, 1000);
    };
  };

  const initWebsocket = () => {
    socketService
      .allocateWebSocket()
      .then((data) => {
        try {
          connectWebSocket(data.data);
        } catch (error: any) {
          console.error('Connection to websocket failed, Reconnect will be attempted in 3 second.', error);
          setTimeout(() => {
            initWebsocket();
          }, 3000);
        }
      })
      .catch((error) => {
        console.error('Connection to websocket failed, Reconnect will be attempted in 3 second.', error);
        setTimeout(() => {
          initWebsocket();
        }, 3000);
      });
  };

  useEffect(() => {
    const confService = new ConfigService();
    if (!shouldInitShellSDK()) {
      setLoading(false);
      return;
    }
    confService
      .getApplicationConfig()
      .then((result) => {
        const data = result.data;
        if (permissionService.isNoAccess(data.applicationConfig.wizardConfig.scope)) {
          console.log("Your role isn't allowed to use SCA");
          setLoading(false);
          history.push('/service-unavalable');
        }
        data.applicationConfig.timeoutConfig.default = data.applicationConfig.timeoutConfig.default
          ? data.applicationConfig.timeoutConfig.default * 1000
          : 15000;
        dispatch(updateApplicationConfig(result.data));
        setLoading(false);
        if (isInIframe()) {
          updateSideNavigation(updateNavBarItems(navBarItems, data.applicationConfig));
          onHideIntroductionButton()
            .then(() => {
              updateSideNavigation(updateNavBarItems(navBarItemsWithoutIntro, data.applicationConfig));
              setDefaultRoute('/policies');
              return history.push('/policies');
            })
            .catch((error) => {
              console.error(error);
            });
        }
        if (data.applicationConfig.wizardConfig.enableWebSocket) {
          initWebsocket();
        }
      })
      .catch((error) => {
        console.log(error);
        setLoading(false);
        history.push('/service-unavalable');
      });
  }, []);
  useEffect(() => {
    if (isInIframe()) {
      updateShellURL({
        pathname: location.pathname,
      });
    }
  }, [location]);

  const onHideIntroPage = (appConfigData: ApplicationConfig) => {
    setShowHide(false);
    updateSideNavigation(updateNavBarItems(navBarItemsWithoutIntro, appConfigData));
    hideIntroduction();
    history.push('/policies');
  };

  useEffect(() => {
    onHideIntroductionButton()
      .then(() => {
        setShowHide(false);
      })
      .catch((error) => {
        console.error(error);
      });
  }, []);

  const sharedRotes = () => {
    return (
      <>
        <Route path="/policies">
          <AccessPolicies />
        </Route>
        <Route path="/Introduction">
          <IntroPage onHideIntroPage={() => onHideIntroPage(appConfig)} showHide={showHide} />
        </Route>
        <Route path="/settings">
          <SelfService />
        </Route>
        <Route path="/gcp_onboarding">
          <GcpOnboarding />
        </Route>
        <Route path="/azure_onboarding">
          <AzureOnboarding />
        </Route>
        <Route path="/service-unavalable">
          <ServiceUnavalable />
        </Route>
        <ProtectedRoute
          path="/create_policy/:provider"
          render={(props) => <Wizard {...props.match.params.provider} wizardMode={WizardMode.Create} />}
          permissions={(match) => [`sca.mgmt.${match.params.provider}.create`]}
        />
        <Route
          path="/view_policy/:provider/:id"
          render={(props) => <Wizard {...props.match.params.id} wizardMode={WizardMode.View} />}
        />
        <Route
          path="/edit_policy/:provider/:id"
          render={(props) => <Wizard {...props.match.params.id} wizardMode={WizardMode.Edit} />}
        />
        <Route path="/cloud_roles">
          <Roles />
        </Route>
        <Route path="/request_form">
          <RequestForm />
        </Route>
        <Route exact={true} path="/">
          <Redirect to={defaultRoute} />
        </Route>
      </>
    );
  };

  const getRoutesForApp = () => {
    if (isInIframe()) {
      return <Switch>{sharedRotes()}</Switch>;
    } else {
      return (
        <Switch>
          <Route path="/policies">
            <AccessPolicies />
          </Route>
          {sharedRotes()}
          <Redirect from="/" to="policies" />
        </Switch>
      );
    }
  };
  const showLoader = loading || fetchUserPermissionsLoading;
  console.log({ loading, fetchUserPermissionsLoading });
  console.log({ showLoader });
  if (showLoader) {
    return (
      <Box data-testid="app" sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '800px' }}>
        <CircularProgress size={150} />
      </Box>
    );
  }
  return (
    <QueryClientProvider client={queryClient}>
      <ReactQueryDevtools initialIsOpen={false} />
      <StyledEngineProvider injectFirst={true}>
        <ThemeProvider theme={scaTheme}>
          <ErrorBoundary
            FallbackComponent={ServiceUnavalable}
            onError={(error) => {
              console.error(error.message);
            }}
          >
            {errorMessage.showErrorMessage && errorMessage.errorDetails ? (
              <ErrorDialog open={errorMessage.showErrorMessage} errorDetails={errorMessage.errorDetails} />
            ) : null}
            <Suspense fallback="loading">
              <div className="App" data-testid="app">
                <div>{getRoutesForApp()}</div>
              </div>
            </Suspense>
          </ErrorBoundary>
        </ThemeProvider>
      </StyledEngineProvider>
    </QueryClientProvider>
  );
}

export default App;