From 7e6d179191526be58822437a76044dd6945b253f Mon Sep 17 00:00:00 2001 From: "Ben Scholzen (DASPRiD)" Date: Mon, 20 Mar 2023 23:10:20 +0100 Subject: [PATCH 1/2] fix: unflatten form data into correct body format The format for bodies with traits dictates that the traits come in through their own object. This becomes especially visible when using an SDK client compiled with a newer version of OpenAPI generator, as they only process properties which are defined in the API schema, which is not the case for flattened properties. Fixes #93 --- .../ory/helpers/user-auth-form.tsx | 49 ++++++++++++++----- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/src/react-components/ory/helpers/user-auth-form.tsx b/src/react-components/ory/helpers/user-auth-form.tsx index 0afccac0e..d21aaeac8 100644 --- a/src/react-components/ory/helpers/user-auth-form.tsx +++ b/src/react-components/ory/helpers/user-auth-form.tsx @@ -12,17 +12,19 @@ import { formStyle } from "../../../theme" import { FilterFlowNodes } from "./filter-flow-nodes" import { SelfServiceFlow } from "./types" +export type UpdateBody = + | UpdateLoginFlowBody + | UpdateRegistrationFlowBody + | UpdateRecoveryFlowBody + | UpdateVerificationFlowBody + | UpdateSettingsFlowBody + export type UserAuthFormAdditionalProps = { onSubmit?: ({ body, event, }: { - body: - | UpdateLoginFlowBody - | UpdateRegistrationFlowBody - | UpdateRecoveryFlowBody - | UpdateVerificationFlowBody - | UpdateSettingsFlowBody + body: UpdateBody event?: React.FormEvent }) => void } @@ -38,6 +40,34 @@ export interface UserAuthFormProps className?: string } +const traitRegexp = /^traits\.(.+)$/ + +const unflattenFormData = (formData: FormData): UpdateBody => { + const body: Record = {} + const traits: Record = {} + + for (const [key, value] of formData) { + if (typeof value !== "string") { + continue + } + + const match = traitRegexp.exec(key) + + if (!match) { + body[key] = value + continue + } + + traits[match[1]] = value + } + + if (Object.keys(traits).length > 0) { + body.traits = traits + } + + return body as unknown as UpdateBody +} + export const UserAuthForm = ({ flow, children, @@ -65,12 +95,7 @@ export const UserAuthForm = ({ const formData = new FormData(form) // map the entire form data to JSON for the request body - let body = Object.fromEntries(formData) as unknown as - | UpdateLoginFlowBody - | UpdateRegistrationFlowBody - | UpdateRecoveryFlowBody - | UpdateVerificationFlowBody - | UpdateSettingsFlowBody + let body = unflattenFormData(formData) // We need the method specified from the name and value of the submit button. // when multiple submit buttons are present, the clicked one's value is used. From ed120c5138872783218a44925a4c26720a40bb0d Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Mon, 26 Jun 2023 17:54:21 +0200 Subject: [PATCH 2/2] feat: use flat library --- package-lock.json | 14 +++++ package.json | 2 + .../ory/helpers/user-auth-form.spec.tsx | 61 +++++++++++++++++++ .../ory/helpers/user-auth-form.tsx | 33 +--------- 4 files changed, 80 insertions(+), 30 deletions(-) create mode 100644 src/react-components/ory/helpers/user-auth-form.spec.tsx diff --git a/package-lock.json b/package-lock.json index 28c17717f..9eff75d7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "@storybook/node-logger": "6.5.16", "@storybook/react": "6.5.16", "@types/express": "^4.17.15", + "@types/flat": "5.0.2", "@types/node": "18.14.0", "@types/react": "18.0.27", "@types/react-dom": "18.0.10", @@ -47,6 +48,7 @@ "eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-storybook": "0.6.11", "eslint-plugin-tsdoc": "0.2.17", + "flat": "5.0.2", "lerna": "5.6.2", "license-checker": "^25.0.1", "nx": "15.7.2", @@ -9869,6 +9871,12 @@ "@types/range-parser": "*" } }, + "node_modules/@types/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-3zsplnP2djeps5P9OyarTxwRpMLoe5Ash8aL9iprw0JxB+FAHjY+ifn4yZUuW4/9hqtnmor6uvjSRzJhiVbrEQ==", + "dev": true + }, "node_modules/@types/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", @@ -39881,6 +39889,12 @@ "@types/range-parser": "*" } }, + "@types/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-3zsplnP2djeps5P9OyarTxwRpMLoe5Ash8aL9iprw0JxB+FAHjY+ifn4yZUuW4/9hqtnmor6uvjSRzJhiVbrEQ==", + "dev": true + }, "@types/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", diff --git a/package.json b/package.json index df51b64aa..598c387e3 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,8 @@ }, "homepage": "https://github.com/ory/elements#readme", "devDependencies": { + "@types/flat": "5.0.2", + "flat": "5.0.2", "@babel/core": "^7.18.10", "@ory/client": "1.1.22", "@ory/integrations": "1.1.0", diff --git a/src/react-components/ory/helpers/user-auth-form.spec.tsx b/src/react-components/ory/helpers/user-auth-form.spec.tsx new file mode 100644 index 000000000..2bf4a58e0 --- /dev/null +++ b/src/react-components/ory/helpers/user-auth-form.spec.tsx @@ -0,0 +1,61 @@ +import { expect, test } from "@playwright/experimental-ct-react" +import { + AuthPage, + RandomString, + registrationFixture, + Traits, +} from "../../../test" +import { UpdateBody, UserAuthForm } from "./user-auth-form" +import { RegistrationSection } from "../sections/registration-section" + +test("user auth form test", async ({ mount }) => { + let body: UpdateBody | undefined + const traits: Record = { + "traits.firstName": { + group: "password", + label: "First Name", + value: RandomString(), + type: "input", + }, + "traits.email": { + group: "password", + label: "Email", + value: RandomString() + "@example.com", + type: "input", + }, + password: { + group: "password", + label: "Password", + value: RandomString(), + type: "input", + }, + csrf_token: { + group: "default", + label: "", + value: RandomString(), + type: "hidden", + }, + } + const component = await mount( + { + body = b + }} + > + + , + ) + + const registrationComponent = new AuthPage(traits, component) + + await registrationComponent.submitForm() + + expect(body).toBeTruthy() + expect(body).toHaveProperty("method", "password") + expect(body).toHaveProperty("password", traits.password.value) + expect(body).toHaveProperty("traits", { + email: traits["traits.email"].value, + firstName: traits["traits.firstName"].value, + }) +}) diff --git a/src/react-components/ory/helpers/user-auth-form.tsx b/src/react-components/ory/helpers/user-auth-form.tsx index d21aaeac8..bcf19dcf5 100644 --- a/src/react-components/ory/helpers/user-auth-form.tsx +++ b/src/react-components/ory/helpers/user-auth-form.tsx @@ -7,6 +7,7 @@ import { } from "@ory/client" import { FilterNodesByGroups } from "@ory/integrations/ui" import cn from "classnames" +import { unflatten } from "flat" import { formStyle } from "../../../theme" import { FilterFlowNodes } from "./filter-flow-nodes" @@ -40,34 +41,6 @@ export interface UserAuthFormProps className?: string } -const traitRegexp = /^traits\.(.+)$/ - -const unflattenFormData = (formData: FormData): UpdateBody => { - const body: Record = {} - const traits: Record = {} - - for (const [key, value] of formData) { - if (typeof value !== "string") { - continue - } - - const match = traitRegexp.exec(key) - - if (!match) { - body[key] = value - continue - } - - traits[match[1]] = value - } - - if (Object.keys(traits).length > 0) { - body.traits = traits - } - - return body as unknown as UpdateBody -} - export const UserAuthForm = ({ flow, children, @@ -95,7 +68,7 @@ export const UserAuthForm = ({ const formData = new FormData(form) // map the entire form data to JSON for the request body - let body = unflattenFormData(formData) + let body = Object.fromEntries(formData) as unknown as UpdateBody // We need the method specified from the name and value of the submit button. // when multiple submit buttons are present, the clicked one's value is used. @@ -109,7 +82,7 @@ export const UserAuthForm = ({ } } - onSubmit({ body, event }) + onSubmit({ body: unflatten(body) as UpdateBody, event }) }, })} {...props}