From 35119b868c14f62ab354364a1a61d32045ca7422 Mon Sep 17 00:00:00 2001 From: Jonas Hungershausen Date: Wed, 27 Nov 2024 17:50:45 +0100 Subject: [PATCH] fix: handle recovery brute force protection (#296) --- packages/elements-react/jest.config.ts | 6 +++ .../src/util/onSubmitRecovery.ts | 49 ++++++++++++++++--- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/packages/elements-react/jest.config.ts b/packages/elements-react/jest.config.ts index fb30c5cf6..bb34ffb9a 100644 --- a/packages/elements-react/jest.config.ts +++ b/packages/elements-react/jest.config.ts @@ -11,6 +11,12 @@ export default { ".+\\.(svg|css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$": "jest-transform-stub", }, + collectCoverageFrom: [ + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.js", + "src/**/*.jsx", + ], moduleFileExtensions: ["ts", "tsx", "js", "jsx"], coverageDirectory: "../../coverage/packages/elements-react", coveragePathIgnorePatterns: ["/node_modules/", "/dist/", ".svg"], diff --git a/packages/elements-react/src/util/onSubmitRecovery.ts b/packages/elements-react/src/util/onSubmitRecovery.ts index abbba7596..d10db63ee 100644 --- a/packages/elements-react/src/util/onSubmitRecovery.ts +++ b/packages/elements-react/src/util/onSubmitRecovery.ts @@ -2,16 +2,21 @@ // SPDX-License-Identifier: Apache-2.0 import { + ContinueWith, FlowType, + GenericError, handleContinueWith, handleFlowError, + instanceOfContinueWithRecoveryUi, + OnRedirectHandler, RecoveryFlow, recoveryUrl, UpdateRecoveryFlowBody, } from "@ory/client-fetch" +import { frontendClient } from "./client" +import { OryClientConfiguration } from "./clientConfiguration" import { OryFlowContainer } from "./flowContainer" import { OnSubmitHandlerProps } from "./submitHandler" -import { frontendClient } from "./client" /** * Use this method to submit a recovery flow. This method is used in the `onSubmit` handler of the recovery form. @@ -63,14 +68,44 @@ export async function onSubmitRecovery( onRestartFlow: () => { onRedirect(recoveryUrl(config), true) }, - onValidationError: (body: RecoveryFlow) => { - setFlowContainer({ - flow: body, - flowType: FlowType.Recovery, - config, - }) + onValidationError: (body: RecoveryFlow | { error: GenericError }) => { + if ("error" in body) { + handleContinueWithRecoveryUIError(body.error, config, onRedirect) + return + } else { + setFlowContainer({ + flow: body, + flowType: FlowType.Recovery, + config, + }) + } }, onRedirect, }), ) } + +function handleContinueWithRecoveryUIError( + error: GenericError, + config: OryClientConfiguration, + onRedirect: OnRedirectHandler, +) { + if ( + "continue_with" in error.details && + Array.isArray(error.details.continue_with) + ) { + const continueWithRecovery = ( + error.details.continue_with as ContinueWith[] + ).find(instanceOfContinueWithRecoveryUi) + if (continueWithRecovery?.action === "show_recovery_ui") { + onRedirect( + config.project.recovery_ui_url + + "?flow=" + + continueWithRecovery?.flow.id, + false, + ) + return + } + } + onRedirect(recoveryUrl(config), true) +}