Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/clever-shirts-talk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---

Fixes 2FA status change not reflected in UI when enabling/disabling TOTP/emailcode
46 changes: 34 additions & 12 deletions apps/meteor/client/views/account/security/TwoFactorEmail.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { Box, Button, Margins } from '@rocket.chat/fuselage';
import { useUser } from '@rocket.chat/ui-contexts';
import { useUser, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import type { ComponentProps } from 'react';
import { useCallback } from 'react';
import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';

import { useEndpointAction } from '../../../hooks/useEndpointAction';

const TwoFactorEmail = (props: ComponentProps<typeof Box>) => {
const { t } = useTranslation();
const user = useUser();
const dispatchToastMessage = useToastMessageDispatch();

const isEnabled = user?.services?.email2fa?.enabled;
const [isEmail2faEnabled, setIsEmail2faEnabled] = useState(user?.services?.email2fa?.enabled);
const [registeringEmail2fa, setRegisteringEmail2fa] = useState(false);

const enable2faAction = useEndpointAction('POST', '/v1/users.2fa.enableEmail', {
successMessage: t('Two-factor_authentication_enabled'),
Expand All @@ -20,25 +22,45 @@ const TwoFactorEmail = (props: ComponentProps<typeof Box>) => {
});

const handleEnable = useCallback(async () => {
await enable2faAction();
}, [enable2faAction]);
if (registeringEmail2fa) return;
setRegisteringEmail2fa(true);

try {
await enable2faAction();
setIsEmail2faEnabled(true);
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
} finally {
setRegisteringEmail2fa(false);
}
}, [enable2faAction, registeringEmail2fa, dispatchToastMessage, t]);

const handleDisable = useCallback(async () => {
await disable2faAction();
}, [disable2faAction]);
if (registeringEmail2fa) return;
setRegisteringEmail2fa(true);

try {
await disable2faAction();
setIsEmail2faEnabled(false);
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
} finally {
setRegisteringEmail2fa(false);
}
}, [disable2faAction, registeringEmail2fa, dispatchToastMessage, t]);

return (
<Box display='flex' flexDirection='column' alignItems='flex-start' mbs={16} {...props}>
<Margins blockEnd={8}>
<Box fontScale='h4'>{t('Two-factor_authentication_email')}</Box>
{isEnabled && (
<Button danger onClick={handleDisable}>
{isEmail2faEnabled ? (
<Button danger onClick={handleDisable} disabled={registeringEmail2fa}>
{t('Disable_two-factor_authentication_email')}
</Button>
)}
{!isEnabled && (
) : (
<>
<Box>{t('Two-factor_authentication_email_is_currently_disabled')}</Box>
<Button primary onClick={handleEnable}>
<Button primary onClick={handleEnable} disabled={registeringEmail2fa}>
{t('Enable_two-factor_authentication_email')}
</Button>
</>
Expand Down
16 changes: 10 additions & 6 deletions apps/meteor/client/views/account/security/TwoFactorTOTP.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,20 @@ const TwoFactorTOTP = (props: TwoFactorTOTPProps): ReactElement => {

const { register, handleSubmit } = useForm<TwoFactorTOTPFormData>({ defaultValues: { authCode: '' } });

const totpEnabled = user?.services?.totp?.enabled;
const [isTotpEnabled, setIsTotpEnabled] = useState(user?.services?.totp?.enabled);

const closeModal = useCallback(() => setModal(null), [setModal]);

useEffect(() => {
const updateCodesRemaining = async (): Promise<void | boolean> => {
if (!totpEnabled) {
if (!isTotpEnabled) {
return false;
}
const result = await checkCodesRemainingFn();
setCodesRemaining(result.remaining);
};
updateCodesRemaining();
}, [checkCodesRemainingFn, setCodesRemaining, totpEnabled]);
}, [checkCodesRemainingFn, setCodesRemaining, isTotpEnabled]);

const handleEnableTotp = useCallback(async () => {
try {
Expand All @@ -73,6 +73,9 @@ const TwoFactorTOTP = (props: TwoFactorTOTPProps): ReactElement => {
return dispatchToastMessage({ type: 'error', message: t('Invalid_two_factor_code') });
}

setIsTotpEnabled(false);
setRegisteringTotp(false);

dispatchToastMessage({ type: 'success', message: t('Two-factor_authentication_disabled') });
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
Expand All @@ -93,6 +96,7 @@ const TwoFactorTOTP = (props: TwoFactorTOTPProps): ReactElement => {
}

setModal(<BackupCodesModal codes={result.codes} onClose={closeModal} />);
setIsTotpEnabled(true);
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
}
Expand Down Expand Up @@ -121,15 +125,15 @@ const TwoFactorTOTP = (props: TwoFactorTOTPProps): ReactElement => {
<Box display='flex' flexDirection='column' alignItems='flex-start' {...props}>
<Margins blockEnd={8}>
<Box fontScale='h4'>{t('Two-factor_authentication_via_TOTP')}</Box>
{!totpEnabled && !registeringTotp && (
{!isTotpEnabled && !registeringTotp && (
<>
<Box>{t('Two-factor_authentication_is_currently_disabled')}</Box>
<Button primary onClick={handleEnableTotp}>
{t('Enable_two-factor_authentication')}
</Button>
</>
)}
{!totpEnabled && registeringTotp && (
{!isTotpEnabled && registeringTotp && (
<>
<Box>{t('Scan_QR_code')}</Box>
<Box>{t('Scan_QR_code_alternative_s')}</Box>
Expand All @@ -143,7 +147,7 @@ const TwoFactorTOTP = (props: TwoFactorTOTPProps): ReactElement => {
</Box>
</>
)}
{totpEnabled && (
{isTotpEnabled && (
<>
<Button danger onClick={handleDisableTotp}>
{t('Disable_two-factor_authentication')}
Expand Down