Skip to content

Commit

Permalink
fix: properly handle form submits in settings flow
Browse files Browse the repository at this point in the history
  • Loading branch information
jonas-jonas committed Dec 23, 2024
1 parent f2294c1 commit 4bb066e
Show file tree
Hide file tree
Showing 19 changed files with 610 additions and 346 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function OryTwoStepCard() {
flow: { ui },
} = useOryFlow()

const { Form } = useComponents()
const { Form, Card } = useComponents()
const { flowType, formState, dispatchFormState } = useOryFlow()

const nodeSorter = useNodeSorter()
Expand Down Expand Up @@ -74,6 +74,7 @@ export function OryTwoStepCard() {
.map((node, k) => <Node node={node} key={k} />)}
{formState.current === "select_method" && (
<>
<Card.Divider />
<AuthMethodList
options={options}
setSelectedGroup={(group) =>
Expand Down
143 changes: 5 additions & 138 deletions packages/elements-react/src/components/form/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,16 @@
// SPDX-License-Identifier: Apache-2.0

import {
FlowType,
OnRedirectHandler,
UpdateLoginFlowBody,
UpdateRecoveryFlowBody,
UpdateRegistrationFlowBody,
UpdateSettingsFlowBody,
UpdateVerificationFlowBody,
isUiNodeAnchorAttributes,
isUiNodeImageAttributes,
isUiNodeInputAttributes,
isUiNodeScriptAttributes,
} from "@ory/client-fetch"
import { ComponentType, PropsWithChildren } from "react"
import { SubmitHandler, useFormContext } from "react-hook-form"
import { useFormContext } from "react-hook-form"
import { useIntl } from "react-intl"
import { useComponents, useOryFlow } from "../../context"
import {
FormValues,
OryCardAuthMethodListItemProps,
OryCardLogoProps,
OryFormRootProps,
Expand All @@ -32,12 +24,6 @@ import {
OryNodeLabelProps,
OryNodeTextProps,
} from "../../types"
import { OryFlowContainer } from "../../util"
import { onSubmitLogin } from "../../util/onSubmitLogin"
import { onSubmitRecovery } from "../../util/onSubmitRecovery"
import { onSubmitRegistration } from "../../util/onSubmitRegistration"
import { onSubmitSettings } from "../../util/onSubmitSettings"
import { onSubmitVerification } from "../../util/onSubmitVerification"
import { OryCardFooterProps } from "../card"
import { OryCardRootProps } from "../card/card"
import { OryCardContentProps } from "../card/content"
Expand All @@ -50,15 +36,15 @@ import {
OrySettingsTotpProps,
OrySettingsWebauthnProps,
} from "../settings"
import { computeDefaultValues } from "./form-helpers"
import { OryFormGroupProps, OryFormGroups } from "./groups"
import { OryMessageContentProps, OryMessageRootProps } from "./messages"
import { OryFormSectionProps } from "./section"
import { OryCardSettingsSectionProps } from "./section"
import {
OryFormOidcButtons,
OryFormOidcRootProps,
OryNodeOidcButtonProps,
} from "./social"
import { useOryFormSubmit } from "./useOryFormSubmit"

/**
* A record of all the components that are used in the OryForm component.
Expand Down Expand Up @@ -141,7 +127,7 @@ export type OryFlowComponents = {
/**
* The SettingsSection component is rendered around each section of the settings.
*/
SettingsSection: ComponentType<OryFormSectionProps>
SettingsSection: ComponentType<OryCardSettingsSectionProps>
/**
* The SettingsSectionContent component is rendered around the content of each section of the settings.
*/
Expand Down Expand Up @@ -232,126 +218,7 @@ export function OryForm({ children, onAfterSubmit }: OryFormProps) {

const intl = useIntl()

const onRedirect: OnRedirectHandler = (url, external) => {
if (external) {
window.location.href = url
return
}

// TODO(jonas): this should somehow be overridable by the user to allow next js specific redirects, or other frameworks.
window.location.href = url
}

const handleSuccess = (flow: OryFlowContainer) => {
flowContainer.setFlowContainer(flow)
methods.reset(computeDefaultValues(flow.flow.ui.nodes))
}

const onSubmit: SubmitHandler<FormValues> = async (data) => {
switch (flowContainer.flowType) {
case FlowType.Login: {
const submitData: UpdateLoginFlowBody = {
...(data as unknown as UpdateLoginFlowBody),
}
if (submitData.method === "code" && data.code) {
submitData.resend = ""
}

await onSubmitLogin(flowContainer, {
onRedirect,
setFlowContainer: handleSuccess,
body: submitData,
})
break
}
case FlowType.Registration: {
const submitData: UpdateRegistrationFlowBody = {
...(data as unknown as UpdateRegistrationFlowBody),
}

if (submitData.method === "code" && submitData.code) {
submitData.resend = ""
}

await onSubmitRegistration(flowContainer, {
onRedirect,
setFlowContainer: handleSuccess,
body: submitData,
})
break
}
case FlowType.Verification:
await onSubmitVerification(flowContainer, {
onRedirect,
setFlowContainer: handleSuccess,
body: data as unknown as UpdateVerificationFlowBody,
})
break
case FlowType.Recovery: {
const submitData: UpdateRecoveryFlowBody = {
...(data as unknown as UpdateRecoveryFlowBody),
}
// TODO: We should probably fix this in Kratos, and give the code priority over the email. However, that would be breaking :(
if (data.code) {
submitData.email = ""
}
await onSubmitRecovery(flowContainer, {
onRedirect,
setFlowContainer: handleSuccess,
body: submitData,
})
break
}
case FlowType.Settings: {
const submitData: UpdateSettingsFlowBody = {
...(data as unknown as UpdateSettingsFlowBody),
}

if ("totp_unlink" in submitData) {
submitData.method = "totp"
}

if (
"lookup_secret_confirm" in submitData ||
"lookup_secret_reveal" in submitData ||
"lookup_secret_regenerate" in submitData
) {
submitData.method = "lookup_secret"
}

// Force the account selection screen on link to provide a better use experience.
// https://github.com/ory/elements/issues/268
// TODO: Maybe this needs to be configurable in the configuration
if (submitData.method === "oidc") {
submitData.upstream_parameters = {
prompt: "select_account",
}
}

if ("webauthn_remove" in submitData) {
submitData.method = "webauthn"
}

if ("passkey_remove" in submitData) {
submitData.method = "passkey"
}

await onSubmitSettings(flowContainer, {
onRedirect,
setFlowContainer: handleSuccess,
body: submitData,
})
break
}
}
if ("password" in data) {
methods.setValue("password", "")
}
if ("code" in data) {
methods.setValue("code", "")
}
onAfterSubmit?.(data.method)
}
const onSubmit = useOryFormSubmit(onAfterSubmit)

const hasMethods =
flowContainer.flow.ui.nodes.filter((node) => {
Expand Down
62 changes: 51 additions & 11 deletions packages/elements-react/src/components/form/section.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,64 @@
// Copyright © 2024 Ory Corp
// SPDX-License-Identifier: Apache-2.0

import { PropsWithChildren } from "react"
import { useComponents } from "../../context/component"
import { OryForm } from "./form"
import { UiNode } from "@ory/client-fetch"
import {
ComponentPropsWithoutRef,
FormEventHandler,
PropsWithChildren,
} from "react"
import { useFormContext } from "react-hook-form"
import { useComponents } from "../../context/component"
import { OryFormProvider } from "./form-provider"
import { useOryFormSubmit } from "./useOryFormSubmit"
import { useOryFlow } from "../../context"

export type OryFormSectionProps = PropsWithChildren<{
nodes?: UiNode[]
}>
type OryFormProps = Omit<
ComponentPropsWithoutRef<"form">,
"action" | "method" | "onSubmit"
>

export function OryFormSection({ children, nodes }: OryFormSectionProps) {
const { Card } = useComponents()
export type OryFormSectionProps = PropsWithChildren<
OryFormProps & {
nodes?: UiNode[]
}
>

export type OryCardSettingsSectionProps = PropsWithChildren & {
action: string
method: string
onSubmit: FormEventHandler<HTMLFormElement>
}

export function OryFormSection({
children,
nodes,
...rest
}: OryFormSectionProps) {
return (
<OryFormProvider nodes={nodes}>
<OryForm>
<Card.SettingsSection>{children}</Card.SettingsSection>
</OryForm>
<OryFormSectionInner {...rest}>{children}</OryFormSectionInner>
</OryFormProvider>
)
}

function OryFormSectionInner({
children,
...rest
}: PropsWithChildren<OryFormProps>) {
const { Card } = useComponents()
const flowContainer = useOryFlow()
const onSubmit = useOryFormSubmit()
const methods = useFormContext()

return (
<Card.SettingsSection
action={flowContainer.flow.ui.action}
method={flowContainer.flow.ui.method}
onSubmit={(e) => void methods.handleSubmit(onSubmit)(e)}
{...rest}
>
{children}
</Card.SettingsSection>
)
}
Loading

0 comments on commit 4bb066e

Please sign in to comment.