From d34f858a4f69bdcd1bee199e732aa87a2540d8f0 Mon Sep 17 00:00:00 2001 From: Riddhiagrawal001 <50551695+Riddhiagrawal001@users.noreply.github.com> Date: Tue, 4 Jun 2024 14:54:48 +0530 Subject: [PATCH] feat: regenerate recovery codes (#776) --- .../AuthModule/TotpAuth/TotpRecoveryCodes.res | 10 +- .../AuthModule/TotpAuth/TotpUtils.res | 8 + .../HSwitchProfile/ModifyTwoFaSettings.res | 207 ++++++++++++++++-- 3 files changed, 200 insertions(+), 25 deletions(-) diff --git a/src/entryPoints/AuthModule/TotpAuth/TotpRecoveryCodes.res b/src/entryPoints/AuthModule/TotpAuth/TotpRecoveryCodes.res index 2cf9f1253..577c067f6 100644 --- a/src/entryPoints/AuthModule/TotpAuth/TotpRecoveryCodes.res +++ b/src/entryPoints/AuthModule/TotpAuth/TotpRecoveryCodes.res @@ -33,14 +33,6 @@ let make = (~setTwoFaPageState, ~onClickDownload, ~setShowNewQR) => { } } - let downloadRecoveryCodes = () => { - open LogicUtils - DownloadUtils.downloadOld( - ~fileName="recoveryCodes.txt", - ~content=JSON.stringifyWithIndent(recoveryCodes->getJsonFromArrayOfString, 3), - ) - } - let copyRecoveryCodes = ev => { open LogicUtils ev->ReactEvent.Mouse.stopPropagation @@ -84,7 +76,7 @@ let make = (~setTwoFaPageState, ~onClickDownload, ~setShowNewQR) => { buttonType={Primary} buttonSize={Small} onClick={_ => { - downloadRecoveryCodes() + TotpUtils.downloadRecoveryCodes(~recoveryCodes) onClickDownload(~skip_2fa=false)->ignore }} /> diff --git a/src/entryPoints/AuthModule/TotpAuth/TotpUtils.res b/src/entryPoints/AuthModule/TotpAuth/TotpUtils.res index 129397c96..80122a827 100644 --- a/src/entryPoints/AuthModule/TotpAuth/TotpUtils.res +++ b/src/entryPoints/AuthModule/TotpAuth/TotpUtils.res @@ -143,3 +143,11 @@ let validateTotpForm = (values: JSON.t, keys: array) => { errors->JSON.Encode.object } + +let downloadRecoveryCodes = (~recoveryCodes) => { + open LogicUtils + DownloadUtils.downloadOld( + ~fileName="recoveryCodes.txt", + ~content=JSON.stringifyWithIndent(recoveryCodes->getJsonFromArrayOfString, 3), + ) +} diff --git a/src/screens/Settings/HSwitchProfile/ModifyTwoFaSettings.res b/src/screens/Settings/HSwitchProfile/ModifyTwoFaSettings.res index baf71b95d..90760493c 100644 --- a/src/screens/Settings/HSwitchProfile/ModifyTwoFaSettings.res +++ b/src/screens/Settings/HSwitchProfile/ModifyTwoFaSettings.res @@ -13,6 +13,7 @@ module Verify2FAModalComponent = { ~setOtp=_ => (), ~recoveryCode="", ~setRecoveryCode=_ => (), + ~showOnlyTotp=false, ) => { open HSwitchSettingTypes
@@ -20,18 +21,20 @@ module Verify2FAModalComponent = { | Totp => <> -

- {"Didn't get a code? "->React.string} - { - setOtp(_ => "") - setErrorMessage(_ => "") - setTwoFaState(_ => RecoveryCode) - }}> - {"Use recovery-code"->React.string} - -

+ +

+ {"Didn't get a code? "->React.string} + { + setOtp(_ => "") + setErrorMessage(_ => "") + setTwoFaState(_ => RecoveryCode) + }}> + {"Use recovery-code"->React.string} + +

+
| RecoveryCode => @@ -98,6 +101,9 @@ module ResetTotp = { } setOtp(_ => "") setButtonState(_ => Button.Normal) + RescriptReactRouter.push( + HSwitchGlobalVars.appendDashboardPath(~url="/account-settings/profile"), + ) } } } @@ -184,7 +190,7 @@ module ResetTotp = {
{ + open LogicUtils + open APIUtils + + let getURL = useGetURL() + let fetchDetails = useGetMethod() + let showToast = ToastState.useShowToast() + let verifyTotpLogic = TotpHooks.useVerifyTotp() + let (showVerifyModal, setShowVerifyModal) = React.useState(_ => false) + let (otpInModal, setOtpInModal) = React.useState(_ => "") + let (buttonState, setButtonState) = React.useState(_ => Button.Normal) + let (recoveryCodes, setRecoveryCodes) = React.useState(_ => []) + let (errorMessage, setErrorMessage) = React.useState(_ => "") + let (screenState, setScreenState) = React.useState(_ => PageLoaderWrapper.Success) + + let generateRecoveryCodes = async () => { + try { + setScreenState(_ => PageLoaderWrapper.Loading) + let url = getURL(~entityName=USERS, ~userType=#GENERATE_RECOVERY_CODES, ~methodType=Get, ()) + let response = await fetchDetails(url) + let recoveryCodesValue = response->getDictFromJsonObject->getStrArray("recovery_codes") + setRecoveryCodes(_ => recoveryCodesValue) + setScreenState(_ => PageLoaderWrapper.Success) + } catch { + | _ => { + setButtonState(_ => Button.Normal) + showToast(~message="Failed to generate recovery codes!", ~toastType=ToastError, ()) + RescriptReactRouter.push( + HSwitchGlobalVars.appendDashboardPath(~url=`/account-settings/profile`), + ) + setScreenState(_ => PageLoaderWrapper.Success) + } + } + } + + let handleModalClose = () => { + RescriptReactRouter.push( + HSwitchGlobalVars.appendDashboardPath(~url=`/account-settings/profile`), + ) + } + + let verifyTOTP = async () => { + try { + setButtonState(_ => Button.Loading) + if otpInModal->String.length > 0 { + let body = [("totp", otpInModal->JSON.Encode.string)]->getJsonFromArrayOfJson + let _ = await verifyTotpLogic(body, Fetch.Post) + setShowVerifyModal(_ => false) + generateRecoveryCodes()->ignore + } else { + showToast(~message="OTP field cannot be empty!", ~toastType=ToastError, ()) + } + setButtonState(_ => Button.Normal) + } catch { + | Exn.Error(e) => { + let err = Exn.message(e)->Option.getOr("Verification Failed") + let errorMessage = err->safeParse->getDictFromJsonObject->getString("message", "") + setOtpInModal(_ => "") + setErrorMessage(_ => errorMessage) + setButtonState(_ => Button.Normal) + } + } + } + + React.useEffect0(() => { + if checkStatusResponse.totp { + generateRecoveryCodes()->ignore + } else { + setShowVerifyModal(_ => true) + } + None + }) + + let copyRecoveryCodes = ev => { + ev->ReactEvent.Mouse.stopPropagation + Clipboard.writeText(JSON.stringifyWithIndent(recoveryCodes->getJsonFromArrayOfString, 3)) + showToast(~message="Copied to Clipboard!", ~toastType=ToastSuccess, ()) + } + + +
+ +
+ ()} + otp={otpInModal} + setOtp={setOtpInModal} + errorMessage + setErrorMessage + showOnlyTotp=true + /> +
+
+
+
+
+
+

+ {"Two factor recovery codes"->React.string} +

+
+
+
+

+ {"Recovery codes provide a way to access your account if you lose your device and can't receive two-factor authentication codes."->React.string} +

+ + +
+
+
+
+
+
+
+ } +} + @react.component let make = () => { open APIUtils @@ -316,18 +486,23 @@ let make = () => { None }) + let pageTitle = switch twofactorAuthType->HSwitchProfileUtils.getTwoFaEnumFromString { + | ResetTotp => "Reset totp" + | RegenerateRecoveryCode => "Regenerate recovery codes" + } +
- +
{switch twofactorAuthType->HSwitchProfileUtils.getTwoFaEnumFromString { | ResetTotp => - | RegenerateRecoveryCode => React.null + | RegenerateRecoveryCode => }}
}