From 0e935a00dab9c9013dcfc2edcd95b48546c7697a Mon Sep 17 00:00:00 2001 From: Riddhiagrawal001 Date: Sun, 6 Oct 2024 20:04:13 +0530 Subject: [PATCH 1/3] chore: two fa inside profile page chnages --- cypress/e2e/connector/connector.cy.js | 16 +- .../HSwitchProfile/ModifyTwoFaSettings.res | 544 ------------------ .../TwoFaSettings/ModifyTwoFaSettings.res | 57 ++ .../TwoFaSettings/RegenerateRC.res | 185 ++++++ .../TwoFaSettings/ResetTotp.res | 239 ++++++++ .../TwoFaSettings/TwoFaHelper.res | 60 ++ 6 files changed, 545 insertions(+), 556 deletions(-) delete mode 100644 src/screens/Settings/HSwitchProfile/ModifyTwoFaSettings.res create mode 100644 src/screens/Settings/HSwitchProfile/TwoFaSettings/ModifyTwoFaSettings.res create mode 100644 src/screens/Settings/HSwitchProfile/TwoFaSettings/RegenerateRC.res create mode 100644 src/screens/Settings/HSwitchProfile/TwoFaSettings/ResetTotp.res create mode 100644 src/screens/Settings/HSwitchProfile/TwoFaSettings/TwoFaHelper.res diff --git a/cypress/e2e/connector/connector.cy.js b/cypress/e2e/connector/connector.cy.js index d37ac1cf1..3241f2d5e 100644 --- a/cypress/e2e/connector/connector.cy.js +++ b/cypress/e2e/connector/connector.cy.js @@ -29,20 +29,14 @@ describe("connector", () => { it("Create a dummy connector", () => { cy.url().should("include", "/dashboard/home"); - cy.get('[data-form-label="Business name"]').should( - "exist", - ); - cy.get("[data-testid=merchant_name]").type( - "test_business", - ); + cy.get('[data-form-label="Business name"]').should("exist"); + cy.get("[data-testid=merchant_name]").type("test_business"); cy.get("[data-button-for=startExploring]").click(); cy.reload(true); cy.get("[data-testid=connectors]").click(); cy.get("[data-testid=paymentprocessors]").click(); cy.contains("Payment Processors").should("be.visible"); - cy.contains("Connect a Dummy Processor").should( - "be.visible", - ); + cy.contains("Connect a Dummy Processor").should("be.visible"); cy.get("[data-button-for=connectNow]").click({ force: true, }); @@ -52,9 +46,7 @@ describe("connector", () => { .find("button") .should("have.length", 4); cy.contains("Stripe Dummy").should("be.visible"); - cy.get('[data-testid="stripe_test"]') - .find("button") - .click({ force: true }); + cy.get('[data-testid="stripe_test"]').find("button").click({ force: true }); cy.url().should("include", "/dashboard/connectors"); cy.contains("Credentials").should("be.visible"); cy.get("[name=connector_account_details\\.api_key]") diff --git a/src/screens/Settings/HSwitchProfile/ModifyTwoFaSettings.res b/src/screens/Settings/HSwitchProfile/ModifyTwoFaSettings.res deleted file mode 100644 index 81f27f467..000000000 --- a/src/screens/Settings/HSwitchProfile/ModifyTwoFaSettings.res +++ /dev/null @@ -1,544 +0,0 @@ -let h2TextStyle = HSwitchUtils.getTextClass((H2, Optional)) -let p2Regular = HSwitchUtils.getTextClass((P2, Regular)) -let p3Regular = HSwitchUtils.getTextClass((P3, Regular)) - -module Verify2FAModalComponent = { - @react.component - let make = ( - ~twoFaState, - ~setTwoFaState, - ~errorMessage, - ~setErrorMessage, - ~otp="", - ~setOtp=_ => (), - ~recoveryCode="", - ~setRecoveryCode=_ => (), - ~showOnlyTotp=false, - ) => { - open HSwitchSettingTypes -
- {switch twoFaState { - | Totp => - <> - - -

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

-
- - - | RecoveryCode => - <> - -

- {"Didn't get a code? "->React.string} - { - setRecoveryCode(_ => "") - setErrorMessage(_ => "") - setTwoFaState(_ => Totp) - }}> - {"Use totp instead"->React.string} - -

- - }} - String.length > 0}> -
{`Error: ${errorMessage}`->React.string}
-
-
- } -} - -module ResetTotp = { - @react.component - let make = (~checkStatusResponse) => { - open LogicUtils - open HSwitchSettingTypes - open APIUtils - - let getURL = useGetURL() - let showToast = ToastState.useShowToast() - let fetchDetails = APIUtils.useGetMethod() - let verifyTotpLogic = TotpHooks.useVerifyTotp() - let verifyRecoveryCodeLogic = TotpHooks.useVerifyRecoveryCode() - let (showVerifyModal, setShowVerifyModal) = React.useState(_ => false) - let (otpInModal, setOtpInModal) = React.useState(_ => "") - let (otp, setOtp) = React.useState(_ => "") - let (recoveryCode, setRecoveryCode) = React.useState(_ => "") - let (buttonState, setButtonState) = React.useState(_ => Button.Normal) - let (totpSecret, setTotpSecret) = React.useState(_ => RegenerateQR) - let (twoFaState, setTwoFaState) = React.useState(_ => Totp) - let (errorMessage, setErrorMessage) = React.useState(_ => "") - - let generateNewSecret = async () => { - try { - setButtonState(_ => Button.Loading) - let url = getURL(~entityName=USERS, ~userType=#RESET_TOTP, ~methodType=Get) - let res = await fetchDetails(url) - setTotpSecret(_ => ShowNewTotp( - res->getDictFromJsonObject->getDictfromDict("secret")->getString("totp_url", ""), - )) - setOtp(_ => "") - setButtonState(_ => Button.Normal) - } catch { - | Exn.Error(e) => { - let err = Exn.message(e)->Option.getOr("Verification Failed") - let errorCode = err->safeParse->getDictFromJsonObject->getString("code", "") - if errorCode->CommonAuthUtils.errorSubCodeMapper === UR_40 { - setShowVerifyModal(_ => true) - } - setOtp(_ => "") - setButtonState(_ => Button.Normal) - RescriptReactRouter.push(GlobalVars.appendDashboardPath(~url="/account-settings/profile")) - } - } - } - - let verifyTOTP = async (~fromModal, ~methodType, ~otp) => { - try { - setButtonState(_ => Button.Loading) - if otpInModal->String.length > 0 || otp->String.length > 0 { - let body = [("totp", otp->JSON.Encode.string)]->getJsonFromArrayOfJson - - let _ = await verifyTotpLogic(body, methodType) - if fromModal { - setShowVerifyModal(_ => false) - generateNewSecret()->ignore - } else { - showToast(~message="Successfully reset the totp !", ~toastType=ToastSuccess) - RescriptReactRouter.push( - GlobalVars.appendDashboardPath(~url="/account-settings/profile"), - ) - } - setOtp(_ => "") - setOtpInModal(_ => "") - } 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", "") - let errorCode = err->safeParse->getDictFromJsonObject->getString("code", "") - if errorCode->CommonAuthUtils.errorSubCodeMapper === UR_42 { - setTotpSecret(_ => RegenerateQR) - } - setOtpInModal(_ => "") - setOtp(_ => "") - setErrorMessage(_ => errorMessage) - setButtonState(_ => Button.Normal) - } - } - } - - let verifyRecoveryCode = async () => { - try { - setButtonState(_ => Button.Loading) - if recoveryCode->String.length > 0 { - let body = [("recovery_code", recoveryCode->JSON.Encode.string)]->getJsonFromArrayOfJson - let _ = await verifyRecoveryCodeLogic(body) - setShowVerifyModal(_ => false) - } else { - showToast(~message="Recovery code cannot be empty!", ~toastType=ToastError) - } - setRecoveryCode(_ => "") - setButtonState(_ => Button.Normal) - } catch { - | Exn.Error(e) => { - let err = Exn.message(e)->Option.getOr("Verification Failed") - let errorMessage = err->safeParse->getDictFromJsonObject->getString("message", "") - setRecoveryCode(_ => "") - setErrorMessage(_ => errorMessage) - setButtonState(_ => Button.Normal) - } - } - } - - let handle2FaVerify = () => { - switch twoFaState { - | Totp => verifyTOTP(~fromModal=true, ~methodType=Post, ~otp={otpInModal}) - | RecoveryCode => verifyRecoveryCode() - } - } - - React.useEffect(() => { - if checkStatusResponse.totp || checkStatusResponse.recovery_code { - generateNewSecret()->ignore - } else { - setShowVerifyModal(_ => true) - } - None - }, []) - - let handleKeyUp = ev => { - open ReactEvent.Keyboard - let key = ev->key - let keyCode = ev->keyCode - - if key === "Enter" || keyCode === 13 { - handle2FaVerify()->ignore - } - } - - React.useEffect(() => { - if otpInModal->String.length == 6 || recoveryCode->String.length == 9 { - Window.addEventListener("keyup", handleKeyUp) - } else { - Window.removeEventListener("keyup", handleKeyUp) - } - - Some( - () => { - Window.removeEventListener("keyup", handleKeyUp) - }, - ) - }, [otpInModal, recoveryCode]) - - let handleModalClose = () => { - RescriptReactRouter.push(GlobalVars.appendDashboardPath(~url=`/account-settings/profile`)) - } - -
- -
- -
-
-
-
-
-
-

{"Enable new 2FA"->React.string}

-
-
- {switch totpSecret { - | ShowNewTotp(totpUrl) => - <> - -
- -
- - | RegenerateQR => - }} -
- {switch totpSecret { - | RegenerateQR => -
-
-
-
- } -} - -module RegenerateRecoveryCodes = { - @react.component - let make = (~checkStatusResponse: HSwitchSettingTypes.checkStatusType) => { - 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(GlobalVars.appendDashboardPath(~url=`/account-settings/profile`)) - setScreenState(_ => PageLoaderWrapper.Success) - } - } - } - - let handleModalClose = () => { - RescriptReactRouter.push(GlobalVars.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, Post) - setShowVerifyModal(_ => false) - generateRecoveryCodes()->ignore - } else { - showToast(~message="OTP field cannot be empty!", ~toastType=ToastError) - } - setOtpInModal(_ => "") - 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.useEffect(() => { - if checkStatusResponse.totp { - generateRecoveryCodes()->ignore - } else { - setShowVerifyModal(_ => true) - } - None - }, []) - - let handleKeyUp = ev => { - open ReactEvent.Keyboard - let key = ev->key - let keyCode = ev->keyCode - - if key === "Enter" || keyCode === 13 { - verifyTOTP()->ignore - } - } - - React.useEffect(() => { - if otpInModal->String.length == 6 { - Window.addEventListener("keyup", handleKeyUp) - } else { - Window.removeEventListener("keyup", handleKeyUp) - } - - Some( - () => { - Window.removeEventListener("keyup", handleKeyUp) - }, - ) - }, [otpInModal]) - - 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 - open HSwitchProfileUtils - - let showToast = ToastState.useShowToast() - let url = RescriptReactRouter.useUrl() - let twofactorAuthType = url.search->LogicUtils.getDictFromUrlSearchParams->Dict.get("type") - let getURL = useGetURL() - let fetchDetails = useGetMethod() - - let (checkStatusResponse, setCheckStatusResponse) = React.useState(_ => - Dict.make()->typedValueForCheckStatus - ) - let (screenState, setScreenState) = React.useState(_ => PageLoaderWrapper.Success) - - let checkTwoFaStatus = async () => { - try { - open LogicUtils - setScreenState(_ => PageLoaderWrapper.Loading) - let url = getURL(~entityName=USERS, ~userType=#CHECK_TWO_FACTOR_AUTH_STATUS, ~methodType=Get) - let res = await fetchDetails(url) - setCheckStatusResponse(_ => res->getDictFromJsonObject->typedValueForCheckStatus) - setScreenState(_ => PageLoaderWrapper.Success) - } catch { - | _ => { - showToast(~message="Failed to fetch 2FA status!", ~toastType=ToastError) - RescriptReactRouter.push(GlobalVars.appendDashboardPath(~url="/account-settings/profile")) - } - } - } - - React.useEffect(() => { - checkTwoFaStatus()->ignore - None - }, []) - - let pageTitle = switch twofactorAuthType->HSwitchProfileUtils.getTwoFaEnumFromString { - | ResetTotp => "Reset totp" - | RegenerateRecoveryCode => "Regenerate recovery codes" - } - - -
- - -
- {switch twofactorAuthType->HSwitchProfileUtils.getTwoFaEnumFromString { - | ResetTotp => - | RegenerateRecoveryCode => - }} -
-} diff --git a/src/screens/Settings/HSwitchProfile/TwoFaSettings/ModifyTwoFaSettings.res b/src/screens/Settings/HSwitchProfile/TwoFaSettings/ModifyTwoFaSettings.res new file mode 100644 index 000000000..0d926f1b2 --- /dev/null +++ b/src/screens/Settings/HSwitchProfile/TwoFaSettings/ModifyTwoFaSettings.res @@ -0,0 +1,57 @@ +@react.component +let make = () => { + open APIUtils + open HSwitchProfileUtils + + let showToast = ToastState.useShowToast() + let url = RescriptReactRouter.useUrl() + let twofactorAuthType = url.search->LogicUtils.getDictFromUrlSearchParams->Dict.get("type") + let getURL = useGetURL() + let fetchDetails = useGetMethod() + + let (checkStatusResponse, setCheckStatusResponse) = React.useState(_ => + Dict.make()->typedValueForCheckStatus + ) + let (screenState, setScreenState) = React.useState(_ => PageLoaderWrapper.Success) + + let checkTwoFaStatus = async () => { + try { + open LogicUtils + setScreenState(_ => PageLoaderWrapper.Loading) + let url = getURL(~entityName=USERS, ~userType=#CHECK_TWO_FACTOR_AUTH_STATUS, ~methodType=Get) + let res = await fetchDetails(url) + setCheckStatusResponse(_ => res->getDictFromJsonObject->typedValueForCheckStatus) + setScreenState(_ => PageLoaderWrapper.Success) + } catch { + | _ => { + showToast(~message="Failed to fetch 2FA status!", ~toastType=ToastError) + RescriptReactRouter.push(GlobalVars.appendDashboardPath(~url="/account-settings/profile")) + } + } + } + + React.useEffect(() => { + checkTwoFaStatus()->ignore + None + }, []) + + let pageTitle = switch twofactorAuthType->HSwitchProfileUtils.getTwoFaEnumFromString { + | ResetTotp => "Reset totp" + | RegenerateRecoveryCode => "Regenerate recovery codes" + } + + +
+ + +
+ {switch twofactorAuthType->HSwitchProfileUtils.getTwoFaEnumFromString { + | ResetTotp => + | RegenerateRecoveryCode => + }} +
+} diff --git a/src/screens/Settings/HSwitchProfile/TwoFaSettings/RegenerateRC.res b/src/screens/Settings/HSwitchProfile/TwoFaSettings/RegenerateRC.res new file mode 100644 index 000000000..0f6d15567 --- /dev/null +++ b/src/screens/Settings/HSwitchProfile/TwoFaSettings/RegenerateRC.res @@ -0,0 +1,185 @@ +let h2TextStyle = HSwitchUtils.getTextClass((H2, Optional)) +let p2Regular = HSwitchUtils.getTextClass((P2, Regular)) +let p3Regular = HSwitchUtils.getTextClass((P3, Regular)) + +@react.component +let make = (~checkStatusResponse: HSwitchSettingTypes.checkStatusType) => { + 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(GlobalVars.appendDashboardPath(~url=`/account-settings/profile`)) + setScreenState(_ => PageLoaderWrapper.Success) + } + } + } + + let handleModalClose = () => { + RescriptReactRouter.push(GlobalVars.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, Post) + setShowVerifyModal(_ => false) + generateRecoveryCodes()->ignore + } else { + showToast(~message="OTP field cannot be empty!", ~toastType=ToastError) + } + setOtpInModal(_ => "") + 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.useEffect(() => { + if checkStatusResponse.totp { + generateRecoveryCodes()->ignore + } else { + setShowVerifyModal(_ => true) + } + None + }, []) + + let handleKeyUp = ev => { + open ReactEvent.Keyboard + let key = ev->key + let keyCode = ev->keyCode + + if key === "Enter" || keyCode === 13 { + verifyTOTP()->ignore + } + } + + React.useEffect(() => { + if otpInModal->String.length == 6 { + Window.addEventListener("keyup", handleKeyUp) + } else { + Window.removeEventListener("keyup", handleKeyUp) + } + + Some( + () => { + Window.removeEventListener("keyup", handleKeyUp) + }, + ) + }, [otpInModal]) + + 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} +

+ + +
+
+
+
+
+
+
+} diff --git a/src/screens/Settings/HSwitchProfile/TwoFaSettings/ResetTotp.res b/src/screens/Settings/HSwitchProfile/TwoFaSettings/ResetTotp.res new file mode 100644 index 000000000..75e61d886 --- /dev/null +++ b/src/screens/Settings/HSwitchProfile/TwoFaSettings/ResetTotp.res @@ -0,0 +1,239 @@ +let h2TextStyle = HSwitchUtils.getTextClass((H2, Optional)) +let p2Regular = HSwitchUtils.getTextClass((P2, Regular)) +let p3Regular = HSwitchUtils.getTextClass((P3, Regular)) + +@react.component +let make = (~checkStatusResponse) => { + open LogicUtils + open HSwitchSettingTypes + open APIUtils + + let getURL = useGetURL() + let showToast = ToastState.useShowToast() + let fetchDetails = APIUtils.useGetMethod() + let verifyTotpLogic = TotpHooks.useVerifyTotp() + let verifyRecoveryCodeLogic = TotpHooks.useVerifyRecoveryCode() + let (showVerifyModal, setShowVerifyModal) = React.useState(_ => false) + let (otpInModal, setOtpInModal) = React.useState(_ => "") + let (otp, setOtp) = React.useState(_ => "") + let (recoveryCode, setRecoveryCode) = React.useState(_ => "") + let (buttonState, setButtonState) = React.useState(_ => Button.Normal) + let (totpSecret, setTotpSecret) = React.useState(_ => RegenerateQR) + let (twoFaState, setTwoFaState) = React.useState(_ => Totp) + let (errorMessage, setErrorMessage) = React.useState(_ => "") + + let generateNewSecret = async () => { + try { + setButtonState(_ => Button.Loading) + let url = getURL(~entityName=USERS, ~userType=#RESET_TOTP, ~methodType=Get) + let res = await fetchDetails(url) + setTotpSecret(_ => ShowNewTotp( + res->getDictFromJsonObject->getDictfromDict("secret")->getString("totp_url", ""), + )) + setOtp(_ => "") + setButtonState(_ => Button.Normal) + } catch { + | Exn.Error(e) => { + let err = Exn.message(e)->Option.getOr("Verification Failed") + let errorCode = err->safeParse->getDictFromJsonObject->getString("code", "") + if errorCode->CommonAuthUtils.errorSubCodeMapper === UR_40 { + setShowVerifyModal(_ => true) + } + setOtp(_ => "") + setButtonState(_ => Button.Normal) + RescriptReactRouter.push(GlobalVars.appendDashboardPath(~url="/account-settings/profile")) + } + } + } + + let verifyTOTP = async (~fromModal, ~methodType, ~otp) => { + try { + setButtonState(_ => Button.Loading) + if otpInModal->String.length > 0 || otp->String.length > 0 { + let body = [("totp", otp->JSON.Encode.string)]->getJsonFromArrayOfJson + + let _ = await verifyTotpLogic(body, methodType) + if fromModal { + setShowVerifyModal(_ => false) + generateNewSecret()->ignore + } else { + showToast(~message="Successfully reset the totp !", ~toastType=ToastSuccess) + RescriptReactRouter.push(GlobalVars.appendDashboardPath(~url="/account-settings/profile")) + } + setOtp(_ => "") + setOtpInModal(_ => "") + } 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", "") + let errorCode = err->safeParse->getDictFromJsonObject->getString("code", "") + if errorCode->CommonAuthUtils.errorSubCodeMapper === UR_42 { + setTotpSecret(_ => RegenerateQR) + } + setOtpInModal(_ => "") + setOtp(_ => "") + setErrorMessage(_ => errorMessage) + setButtonState(_ => Button.Normal) + } + } + } + + let verifyRecoveryCode = async () => { + try { + setButtonState(_ => Button.Loading) + if recoveryCode->String.length > 0 { + let body = [("recovery_code", recoveryCode->JSON.Encode.string)]->getJsonFromArrayOfJson + let _ = await verifyRecoveryCodeLogic(body) + setShowVerifyModal(_ => false) + } else { + showToast(~message="Recovery code cannot be empty!", ~toastType=ToastError) + } + setRecoveryCode(_ => "") + setButtonState(_ => Button.Normal) + } catch { + | Exn.Error(e) => { + let err = Exn.message(e)->Option.getOr("Verification Failed") + let errorMessage = err->safeParse->getDictFromJsonObject->getString("message", "") + setRecoveryCode(_ => "") + setErrorMessage(_ => errorMessage) + setButtonState(_ => Button.Normal) + } + } + } + + let handle2FaVerify = () => { + switch twoFaState { + | Totp => verifyTOTP(~fromModal=true, ~methodType=Post, ~otp={otpInModal}) + | RecoveryCode => verifyRecoveryCode() + } + } + + React.useEffect(() => { + if checkStatusResponse.totp || checkStatusResponse.recovery_code { + generateNewSecret()->ignore + } else { + setShowVerifyModal(_ => true) + } + None + }, []) + + let handleKeyUp = ev => { + open ReactEvent.Keyboard + let key = ev->key + let keyCode = ev->keyCode + + if key === "Enter" || keyCode === 13 { + handle2FaVerify()->ignore + } + } + + React.useEffect(() => { + if otpInModal->String.length == 6 || recoveryCode->String.length == 9 { + Window.addEventListener("keyup", handleKeyUp) + } else { + Window.removeEventListener("keyup", handleKeyUp) + } + + Some( + () => { + Window.removeEventListener("keyup", handleKeyUp) + }, + ) + }, [otpInModal, recoveryCode]) + + let handleModalClose = () => { + RescriptReactRouter.push(GlobalVars.appendDashboardPath(~url=`/account-settings/profile`)) + } + +
+ +
+ +
+
+
+
+
+
+

{"Enable new 2FA"->React.string}

+
+
+ {switch totpSecret { + | ShowNewTotp(totpUrl) => + <> + +
+ +
+ + | RegenerateQR => + }} +
+ {switch totpSecret { + | RegenerateQR => +
+
+
+
+} diff --git a/src/screens/Settings/HSwitchProfile/TwoFaSettings/TwoFaHelper.res b/src/screens/Settings/HSwitchProfile/TwoFaSettings/TwoFaHelper.res new file mode 100644 index 000000000..9d0ea6d5f --- /dev/null +++ b/src/screens/Settings/HSwitchProfile/TwoFaSettings/TwoFaHelper.res @@ -0,0 +1,60 @@ +let p2Regular = HSwitchUtils.getTextClass((P2, Regular)) + +module Verify2FAModalComponent = { + @react.component + let make = ( + ~twoFaState, + ~setTwoFaState, + ~errorMessage, + ~setErrorMessage, + ~otp="", + ~setOtp=_ => (), + ~recoveryCode="", + ~setRecoveryCode=_ => (), + ~showOnlyTotp=false, + ) => { + open HSwitchSettingTypes +
+ {switch twoFaState { + | Totp => + <> + + +

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

+
+ + + | RecoveryCode => + <> + +

+ {"Didn't get a code? "->React.string} + { + setRecoveryCode(_ => "") + setErrorMessage(_ => "") + setTwoFaState(_ => Totp) + }}> + {"Use totp instead"->React.string} + +

+ + }} + String.length > 0}> +
{`Error: ${errorMessage}`->React.string}
+
+
+ } +} From 83e6a7c7dad12048893f6f3c1eec78b67beff977 Mon Sep 17 00:00:00 2001 From: Riddhiagrawal001 Date: Sun, 6 Oct 2024 20:05:04 +0530 Subject: [PATCH 2/3] chore: formatting fix --- .../AuthModule/Common/CommonAuth.res | 76 +++++++++---------- .../AuthModule/TwoFaAuth/TwoFaAuth.res | 36 ++++----- 2 files changed, 56 insertions(+), 56 deletions(-) diff --git a/src/entryPoints/AuthModule/Common/CommonAuth.res b/src/entryPoints/AuthModule/Common/CommonAuth.res index eac039b66..5eed69d9b 100644 --- a/src/entryPoints/AuthModule/Common/CommonAuth.res +++ b/src/entryPoints/AuthModule/Common/CommonAuth.res @@ -1,24 +1,24 @@ module TermsAndCondition = { @react.component let make = () => { - -
- {"By continuing, you agree to our "->React.string} - - {"Terms of Service"->React.string} - - {" & "->React.string} - - {"Privacy Policy"->React.string} - -
-
+ +
+ {"By continuing, you agree to our "->React.string} + + {"Terms of Service"->React.string} + + {" & "->React.string} + + {"Privacy Policy"->React.string} + +
+
} } @@ -28,12 +28,12 @@ module PageFooterSection = {
- +
} @@ -82,17 +82,17 @@ module Header = {
{prefix->React.string}
-
{ - form.resetFieldState("email") - form.reset(JSON.Encode.object(Dict.make())->Nullable.make) - setAuthType(_ => authType) - GlobalVars.appendDashboardPath(~url=path)->RescriptReactRouter.push - }} - id="card-subtitle" - className={`font-semibold ${textColor.primaryNormal} cursor-pointer`}> - {sufix->React.string} -
+
{ + form.resetFieldState("email") + form.reset(JSON.Encode.object(Dict.make())->Nullable.make) + setAuthType(_ => authType) + GlobalVars.appendDashboardPath(~url=path)->RescriptReactRouter.push + }} + id="card-subtitle" + className={`font-semibold ${textColor.primaryNormal} cursor-pointer`}> + {sufix->React.string} +
} @@ -119,9 +119,9 @@ module Header = { -

- {cardHeaderText->React.string} -

+

+ {cardHeaderText->React.string} +

{switch authType { | LoginWithPassword | LoginWithEmail => diff --git a/src/entryPoints/AuthModule/TwoFaAuth/TwoFaAuth.res b/src/entryPoints/AuthModule/TwoFaAuth/TwoFaAuth.res index cb4fabffd..b648d099c 100644 --- a/src/entryPoints/AuthModule/TwoFaAuth/TwoFaAuth.res +++ b/src/entryPoints/AuthModule/TwoFaAuth/TwoFaAuth.res @@ -292,24 +292,24 @@ let make = (~setAuthStatus, ~authType, ~setAuthType) => { | _ => React.null }} -
- {switch authType { - | LoginWithPassword - | LoginWithEmail - | ResetPassword - | ForgetPassword - | ResendVerifyEmail - | SignUP => - - | _ => React.null - }} -
+
+ {switch authType { + | LoginWithPassword + | LoginWithEmail + | ResetPassword + | ForgetPassword + | ResendVerifyEmail + | SignUP => + + | _ => React.null + }} +
{note}
From 2cff9ae8998933a819d70b452e81e41e20988f84 Mon Sep 17 00:00:00 2001 From: Riddhiagrawal001 Date: Mon, 7 Oct 2024 16:58:25 +0530 Subject: [PATCH 3/3] chore: changes for comments --- .../TwoFaSettings/TwoFaHelper.res | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/screens/Settings/HSwitchProfile/TwoFaSettings/TwoFaHelper.res b/src/screens/Settings/HSwitchProfile/TwoFaSettings/TwoFaHelper.res index 9d0ea6d5f..8f157e8d6 100644 --- a/src/screens/Settings/HSwitchProfile/TwoFaSettings/TwoFaHelper.res +++ b/src/screens/Settings/HSwitchProfile/TwoFaSettings/TwoFaHelper.res @@ -14,6 +14,13 @@ module Verify2FAModalComponent = { ~showOnlyTotp=false, ) => { open HSwitchSettingTypes + let handleOnClick = (~stateToSet) => { + setOtp(_ => "") + setRecoveryCode(_ => "") + setErrorMessage(_ => "") + setTwoFaState(_ => stateToSet) + } +
{switch twoFaState { | Totp => @@ -24,11 +31,7 @@ module Verify2FAModalComponent = { {"Didn't get a code? "->React.string} { - setOtp(_ => "") - setErrorMessage(_ => "") - setTwoFaState(_ => RecoveryCode) - }}> + onClick={_ => handleOnClick(~stateToSet=RecoveryCode)}> {"Use recovery-code"->React.string}

@@ -42,17 +45,13 @@ module Verify2FAModalComponent = { {"Didn't get a code? "->React.string} { - setRecoveryCode(_ => "") - setErrorMessage(_ => "") - setTwoFaState(_ => Totp) - }}> + onClick={_ => handleOnClick(~stateToSet=Totp)}> {"Use totp instead"->React.string}

}} - String.length > 0}> + LogicUtils.isNonEmptyString}>
{`Error: ${errorMessage}`->React.string}