diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f04c24..eeb1f71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,14 @@ -### v.2.5.0 +## v.2.5.1 + +### Security + +- Update JS dependencies with high CVE + +### Fixes and improvements + +- Fix form submissions losing values when field names contain spaces or non-word characters + +## v.2.5.0 ### Features @@ -27,7 +37,7 @@ - Fix manager and presenter views while presentation conversion has no slide count yet - Fix crash on event manager pages when an event has multiple activity leaders -### v.2.4.0 +## v.2.4.0 ### ⚠️ Breaking changes @@ -59,7 +69,7 @@ - Fix italian translation (#179) - Fix random poll choices (#184) -### v.2.3.2 +## v.2.3.2 ### Fixes and improvements @@ -71,7 +81,7 @@ - Fix event code length validation (min: 5, max: 10) - Fix presentation upload progress when editing an event -### v.2.3.1 +## v.2.3.1 ### Fixes and improvements @@ -82,7 +92,7 @@ - Add option to force login to submit quizzes - Fix url with question mark being flagged as a question -### v.2.3.0 +## v.2.3.0 ### Features diff --git a/lib/claper_web/live/event_live/form_component.ex b/lib/claper_web/live/event_live/form_component.ex index b601e45..80ce149 100644 --- a/lib/claper_web/live/event_live/form_component.ex +++ b/lib/claper_web/live/event_live/form_component.ex @@ -61,7 +61,7 @@ defmodule ClaperWeb.EventLive.FormComponent do form={f} labelClass="text-white" fieldClass="bg-gray-700 text-white" - key={safe_field_atom(field.name)} + key={field_key(field.name)} name={field.name} required={field.required} value={ @@ -75,7 +75,7 @@ defmodule ClaperWeb.EventLive.FormComponent do form={f} labelClass="text-white" fieldClass="bg-gray-700 text-white" - key={safe_field_atom(field.name)} + key={field_key(field.name)} name={field.name} required={field.required} value={ @@ -176,13 +176,12 @@ defmodule ClaperWeb.EventLive.FormComponent do end end - defp safe_field_atom(name) when is_binary(name) do - String.to_existing_atom(name) - rescue - ArgumentError -> - if Regex.match?(~r/^[\w]{1,100}$/, name), - do: String.to_atom(name), - else: :invalid_field + defp field_key(name) when is_binary(name) do + try do + String.to_existing_atom(name) + rescue + ArgumentError -> String.to_atom(name) + end end def toggle_form(js \\ %JS{}) do diff --git a/test/claper_web/controllers/stat_controller_test.exs b/test/claper_web/controllers/stat_controller_test.exs new file mode 100644 index 0000000..d74952c --- /dev/null +++ b/test/claper_web/controllers/stat_controller_test.exs @@ -0,0 +1,62 @@ +defmodule ClaperWeb.StatControllerTest do + use ClaperWeb.ConnCase, async: true + + import Claper.{AccountsFixtures, FormsFixtures, PresentationsFixtures} + + describe "POST /export/forms/:form_id" do + setup %{conn: conn} do + owner = confirmed_user_fixture() + presentation_file = presentation_file_fixture(%{user: owner}, [:event]) + + form = + form_fixture(%{ + presentation_file_id: presentation_file.id, + title: "My Form", + fields: [ + %{name: "First Name", type: "text", required: false}, + %{name: "Email Address", type: "email", required: false} + ] + }) + + %{conn: log_in_user(conn, owner), form: form, event: presentation_file.event} + end + + test "exports CSV with field names containing spaces as headers and values", %{ + conn: conn, + form: form, + event: event + } do + {:ok, _submit} = + Claper.Forms.create_or_update_form_submit(event.uuid, %{ + "attendee_identifier" => "attendee-1", + "form_id" => form.id, + "response" => %{"First Name" => "Ada", "Email Address" => "ada@example.com"} + }) + + conn = post(conn, ~p"/export/forms/#{form.id}") + + assert response_content_type(conn, :csv) + body = response(conn, 200) + + [header_line, data_line | _] = String.split(body, "\r\n", trim: true) + + assert header_line =~ "First Name" + assert header_line =~ "Email Address" + + # The CSV row must contain the values, proving header→response key + # alignment works for space-containing field names. Regression: when + # submissions were saved under a wrong key, the CSV row was empty. + assert data_line =~ "Ada" + assert data_line =~ "ada@example.com" + end + + test "returns 403 for users who don't own the event", %{form: form} do + stranger = confirmed_user_fixture() + conn = build_conn() |> log_in_user(stranger) + + conn = post(conn, ~p"/export/forms/#{form.id}") + + assert response(conn, 403) == "Forbidden" + end + end +end diff --git a/test/claper_web/live/event_live/form_component_test.exs b/test/claper_web/live/event_live/form_component_test.exs new file mode 100644 index 0000000..a89b0d8 --- /dev/null +++ b/test/claper_web/live/event_live/form_component_test.exs @@ -0,0 +1,72 @@ +defmodule ClaperWeb.EventLive.FormComponentTest do + use ClaperWeb.ConnCase, async: true + + import Phoenix.LiveViewTest + + import Claper.{AccountsFixtures, FormsFixtures, PresentationsFixtures} + + alias ClaperWeb.EventLive.FormComponent + + defp setup_form(field_attrs) do + presentation_file = presentation_file_fixture(%{}, [:event]) + + form = + form_fixture(%{ + presentation_file_id: presentation_file.id, + fields: field_attrs + }) + + %{event: presentation_file.event, form: form} + end + + describe "rendering fields with non-alphanumeric names" do + test "renders an input whose name uses the original field name (with spaces)" do + %{event: event, form: form} = + setup_form([ + %{name: "First Name", type: "text", required: false}, + %{name: "Email Address", type: "email", required: false} + ]) + + html = + render_component(FormComponent, + id: "form-component", + form: form, + current_user: user_fixture(), + attendee_identifier: nil, + event: event, + current_form_submit: nil + ) + + # The HTML name attribute must contain the actual field name — + # regression: a previous version mapped any non-\w name to :invalid_field, + # so all space-containing fields collided on `form_submit[invalid_field]` + # and submissions were saved under the wrong key. + assert html =~ ~s(name="form_submit[First Name]") + assert html =~ ~s(name="form_submit[Email Address]") + refute html =~ "invalid_field" + end + + test "preserves submitted values when re-rendering for fields with spaces" do + %{event: event, form: form} = + setup_form([ + %{name: "First Name", type: "text", required: false} + ]) + + form_submit = %Claper.Forms.FormSubmit{ + response: %{"First Name" => "Ada"} + } + + html = + render_component(FormComponent, + id: "form-component", + form: form, + current_user: user_fixture(), + attendee_identifier: nil, + event: event, + current_form_submit: form_submit + ) + + assert html =~ ~s(value="Ada") + end + end +end