refactor: remove `rootUrlChange` from meteor (#35945)

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Guilherme Gazzo <guilherme@gazzo.xyz>
ci/docker-build-logs
Júlia Jaeger Foresti 11 months ago committed by GitHub
parent 3a1b59237f
commit e665a7e24e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      apps/meteor/client/components/FingerprintChangeModalConfirmation.tsx
  2. 98
      apps/meteor/client/hooks/useFingerprintChange.tsx
  3. 61
      apps/meteor/client/hooks/useRootUrlChange.tsx
  4. 16
      apps/meteor/client/hooks/useTwoFactorAuthSetupCheck.tsx
  5. 1
      apps/meteor/client/startup/index.ts
  6. 131
      apps/meteor/client/startup/rootUrlChange.ts
  7. 8
      apps/meteor/client/views/root/MainLayout/LoggedInArea.tsx
  8. 12
      apps/meteor/client/views/root/MainLayout/TwoFactorAuthSetupCheck.tsx
  9. 1
      packages/i18n/src/locales/en.i18n.json

@ -8,12 +8,14 @@ import GenericModal from './GenericModal';
type FingerprintChangeModalConfirmationProps = {
onConfirm: () => void;
onCancel: () => void;
onClose: () => void;
newWorkspace: boolean;
};
const FingerprintChangeModalConfirmation = ({
onConfirm,
onCancel,
onClose,
newWorkspace,
}: FingerprintChangeModalConfirmationProps): ReactElement => {
const { t } = useTranslation();
@ -25,6 +27,7 @@ const FingerprintChangeModalConfirmation = ({
onCancel={onCancel}
cancelText={t('Back')}
confirmText={newWorkspace ? t('Confirm_new_workspace') : t('Confirm_configuration_update')}
onClose={onClose}
>
<Box
is='p'

@ -0,0 +1,98 @@
import { useEndpoint, useRole, useSetModal, useSetting, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import { useMutation } from '@tanstack/react-query';
import { useEffect, useReducer } from 'react';
import { useTranslation } from 'react-i18next';
import FingerprintChangeModal from '../components/FingerprintChangeModal';
import FingerprintChangeModalConfirmation from '../components/FingerprintChangeModalConfirmation';
const reducer = (
state: { openModal: boolean; openConfirmation: boolean; newWorkspace?: boolean },
action: { type: 'openModal' | 'openConfirmation' | 'closeModal'; newWorkspace?: boolean },
) => {
switch (action.type) {
case 'openModal':
return { openModal: true, openConfirmation: false, newWorkspace: undefined };
case 'openConfirmation':
return { openModal: false, openConfirmation: true, newWorkspace: action.newWorkspace };
case 'closeModal':
return { openModal: false, openConfirmation: false, newWorkspace: undefined };
default:
return state;
}
};
export const useFingerprintChange = () => {
const { t } = useTranslation();
const dispatchToastMessage = useToastMessageDispatch();
const isAdmin = useRole('admin');
const setModal = useSetModal();
const deploymentFingerPrintVerified = useSetting('Deployment_FingerPrint_Verified', true);
const fingerprintEndpoint = useEndpoint('POST', '/v1/fingerprint');
const [{ openConfirmation, openModal, newWorkspace }, dispatch] = useReducer(reducer, {
openModal: false,
openConfirmation: false,
newWorkspace: undefined,
});
const { mutate: fingerPrintMutation } = useMutation({
mutationKey: ['settings', 'Deployment_FingerPrint_Verified'],
mutationFn: async (setDeploymentAs: 'new-workspace' | 'updated-configuration') => {
const result = await fingerprintEndpoint({ setDeploymentAs });
return {
...result,
setDeploymentAs,
};
},
onSuccess: ({ setDeploymentAs }) => {
if (setDeploymentAs === 'new-workspace') {
return dispatchToastMessage({ type: 'success', message: t('New_workspace_confirmed') });
}
return dispatchToastMessage({ type: 'success', message: t('Configuration_update_confirmed') });
},
});
useEffect(() => {
if (!isAdmin) {
return;
}
if (deploymentFingerPrintVerified === null || deploymentFingerPrintVerified === true) {
return;
}
dispatch({ type: 'openModal' });
return () => {
dispatch({ type: 'closeModal' });
};
}, [deploymentFingerPrintVerified, isAdmin]);
useEffect(() => {
if (!openModal && !openConfirmation) {
setModal(null);
}
if (openModal) {
setModal(
<FingerprintChangeModal
onConfirm={() => dispatch({ type: 'openConfirmation', newWorkspace: true })}
onCancel={() => dispatch({ type: 'openConfirmation', newWorkspace: false })}
onClose={() => dispatch({ type: 'closeModal' })}
/>,
);
}
if (openConfirmation && newWorkspace !== undefined) {
setModal(
<FingerprintChangeModalConfirmation
onConfirm={() => {
fingerPrintMutation(newWorkspace ? 'new-workspace' : 'updated-configuration');
dispatch({ type: 'closeModal' });
}}
onCancel={() => dispatch({ type: 'openModal' })}
onClose={() => dispatch({ type: 'closeModal' })}
newWorkspace={newWorkspace}
/>,
);
}
}, [fingerPrintMutation, setModal, openConfirmation, openModal, newWorkspace]);
};

@ -0,0 +1,61 @@
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
import { useRole, useSetModal, useSetting, useSettingSetValue, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import { useMutation } from '@tanstack/react-query';
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import UrlChangeModal from '../components/UrlChangeModal';
export const useRootUrlChange = () => {
const { t } = useTranslation();
const dispatchToastMessage = useToastMessageDispatch();
const isAdmin = useRole('admin');
const setModal = useSetModal();
const closeModal = useEffectEvent(() => setModal(null));
const currentUrl = location.origin + window.__meteor_runtime_config__.ROOT_URL_PATH_PREFIX;
const siteUrl = useSetting('Site_Url', '');
const documentDomain = useSetting('Document_Domain', '');
const setSiteUrl = useSettingSetValue('Site_Url');
const {
mutate: siteUrlMutation,
isPending,
isSuccess,
} = useMutation({
mutationKey: ['settings', 'Site_Url'],
mutationFn: async (url: string) => {
await setSiteUrl(url);
return { url };
},
onSuccess: ({ url }) => dispatchToastMessage({ type: 'success', message: t('Saved_new_url_site_is__url__', { url }) }),
onError: () => dispatchToastMessage({ type: 'error', message: t('Something_went_wrong') }),
});
useEffect(() => {
if (!isAdmin) {
return;
}
if (!siteUrl) {
return;
}
if (isPending || isSuccess) {
return;
}
if (window.__meteor_runtime_config__.ROOT_URL.replace(/\/$/, '') === currentUrl) {
return;
}
const onConfirm = () => {
closeModal();
siteUrlMutation(currentUrl);
};
setModal(<UrlChangeModal onClose={closeModal} onConfirm={onConfirm} siteUrl={siteUrl} currentUrl={currentUrl} />);
if (documentDomain) {
window.document.domain = documentDomain;
}
return closeModal;
}, [currentUrl, documentDomain, siteUrlMutation, siteUrl, isAdmin, isPending, isSuccess, setModal, closeModal]);
};

@ -0,0 +1,16 @@
import { useSetModal } from '@rocket.chat/ui-contexts';
import { useEffect } from 'react';
import { useRequire2faSetup } from '../views/hooks/useRequire2faSetup';
import TwoFactorRequiredModal from '../views/root/MainLayout/TwoFactorRequiredModal';
export const useTwoFactorAuthSetupCheck = () => {
const setModal = useSetModal();
const require2faSetup = useRequire2faSetup();
useEffect(() => {
if (require2faSetup) {
setModal(<TwoFactorRequiredModal />);
}
}, [setModal, require2faSetup]);
};

@ -12,7 +12,6 @@ import './messageObserve';
import './messageTypes';
import './reloadRoomAfterLogin';
import './roles';
import './rootUrlChange';
import './routes';
import './slashCommands';
import './startup';

@ -1,131 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { hasRole } from '../../app/authorization/client';
import { Roles } from '../../app/models/client';
import { settings } from '../../app/settings/client';
import { sdk } from '../../app/utils/client/lib/SDKClient';
import { t } from '../../app/utils/lib/i18n';
import FingerprintChangeModal from '../components/FingerprintChangeModal';
import FingerprintChangeModalConfirmation from '../components/FingerprintChangeModalConfirmation';
import UrlChangeModal from '../components/UrlChangeModal';
import { imperativeModal } from '../lib/imperativeModal';
import { dispatchToastMessage } from '../lib/toast';
import { isSyncReady } from '../lib/userData';
Meteor.startup(() => {
Tracker.autorun((c) => {
const userId = Meteor.userId();
if (!userId) {
return;
}
if (!Roles.ready.get() || !isSyncReady.get()) {
return;
}
if (hasRole(userId, 'admin') === false) {
return c.stop();
}
const siteUrl = settings.get('Site_Url');
if (!siteUrl) {
return;
}
const currentUrl = location.origin + window.__meteor_runtime_config__.ROOT_URL_PATH_PREFIX;
if (window.__meteor_runtime_config__.ROOT_URL.replace(/\/$/, '') !== currentUrl) {
const confirm = (): void => {
imperativeModal.close();
void sdk.call('saveSetting', 'Site_Url', currentUrl).then(() => {
dispatchToastMessage({ type: 'success', message: t('Saved') });
});
};
imperativeModal.open({
component: UrlChangeModal,
props: {
onConfirm: confirm,
siteUrl,
currentUrl,
onClose: imperativeModal.close,
},
});
}
const documentDomain = settings.get('Document_Domain');
if (documentDomain) {
window.document.domain = documentDomain;
}
return c.stop();
});
});
Meteor.startup(() => {
Tracker.autorun((c) => {
const userId = Meteor.userId();
if (!userId) {
return;
}
if (!Roles.ready.get() || !isSyncReady.get()) {
return;
}
if (hasRole(userId, 'admin') === false) {
return c.stop();
}
const deploymentFingerPrintVerified = settings.get('Deployment_FingerPrint_Verified');
if (deploymentFingerPrintVerified == null || deploymentFingerPrintVerified === true) {
return;
}
const updateWorkspace = (): void => {
imperativeModal.close();
void sdk.rest.post('/v1/fingerprint', { setDeploymentAs: 'updated-configuration' }).then(() => {
dispatchToastMessage({ type: 'success', message: t('Configuration_update_confirmed') });
});
};
const setNewWorkspace = (): void => {
imperativeModal.close();
void sdk.rest.post('/v1/fingerprint', { setDeploymentAs: 'new-workspace' }).then(() => {
dispatchToastMessage({ type: 'success', message: t('New_workspace_confirmed') });
});
};
const openModal = (): void => {
imperativeModal.open({
component: FingerprintChangeModal,
props: {
onConfirm: () => {
imperativeModal.open({
component: FingerprintChangeModalConfirmation,
props: {
onConfirm: setNewWorkspace,
onCancel: openModal,
newWorkspace: true,
},
});
},
onCancel: () => {
imperativeModal.open({
component: FingerprintChangeModalConfirmation,
props: {
onConfirm: updateWorkspace,
onCancel: openModal,
newWorkspace: false,
},
});
},
onClose: imperativeModal.close,
},
});
};
openModal();
return c.stop();
});
});

@ -4,7 +4,10 @@ import type { ReactNode } from 'react';
import { useCustomEmoji } from '../../../hooks/customEmoji/useCustomEmoji';
import { useNotificationUserCalendar } from '../../../hooks/notification/useNotificationUserCalendar';
import { useNotifyUser } from '../../../hooks/notification/useNotifyUser';
import { useFingerprintChange } from '../../../hooks/useFingerprintChange';
import { useRestrictedRoles } from '../../../hooks/useRestrictedRoles';
import { useRootUrlChange } from '../../../hooks/useRootUrlChange';
import { useTwoFactorAuthSetupCheck } from '../../../hooks/useTwoFactorAuthSetupCheck';
import { useUnread } from '../../../hooks/useUnread';
import { useForceLogout } from '../hooks/useForceLogout';
import { useOTRMessaging } from '../hooks/useOTRMessaging';
@ -29,6 +32,11 @@ const LoggedInArea = ({ children }: { children: ReactNode }) => {
useStoreCookiesOnLogin(user._id);
useCustomEmoji();
useRestrictedRoles();
// These 3 hooks below need to be called in this order due to the way our `setModal` works.
// TODO: reevaluate `useSetModal`
useFingerprintChange();
useRootUrlChange();
useTwoFactorAuthSetupCheck();
return children;
};

@ -1,11 +1,10 @@
import { FeaturePreview, FeaturePreviewOff, FeaturePreviewOn } from '@rocket.chat/ui-client';
import { useLayout, useSetModal } from '@rocket.chat/ui-contexts';
import { useLayout } from '@rocket.chat/ui-contexts';
import type { ReactElement, ReactNode } from 'react';
import { lazy, useLayoutEffect } from 'react';
import { lazy } from 'react';
import LayoutWithSidebar from './LayoutWithSidebar';
import LayoutWithSidebarV2 from './LayoutWithSidebarV2';
import TwoFactorRequiredModal from './TwoFactorRequiredModal';
import { useRequire2faSetup } from '../../hooks/useRequire2faSetup';
const AccountSecurityPage = lazy(() => import('../../account/security/AccountSecurityPage'));
@ -13,13 +12,6 @@ const AccountSecurityPage = lazy(() => import('../../account/security/AccountSec
const TwoFactorAuthSetupCheck = ({ children }: { children: ReactNode }): ReactElement => {
const { isEmbedded: embeddedLayout } = useLayout();
const require2faSetup = useRequire2faSetup();
const setModal = useSetModal();
useLayoutEffect(() => {
if (require2faSetup) {
setModal(<TwoFactorRequiredModal />);
}
}, [setModal, require2faSetup]);
if (require2faSetup) {
return (

@ -4480,6 +4480,7 @@
"Save_your_encryption_password": "Save your encryption password",
"Save_your_encryption_password_to_access": "Save your end-to-end encryption password to access",
"Saved": "Saved",
"Saved_new_url_site_is__url__": "Saved, new url site is: {{url}}",
"Saving": "Saving",
"Scan_QR_code": "Using an authenticator app like Google Authenticator, Authy or Duo, scan the QR code. It will display a 6 digit code which you need to enter below.",
"Scan_QR_code_alternative_s": "If you cannot scan the QR code, you may enter the following code manually into the authenticator app instead:",

Loading…
Cancel
Save