From 6c3334b134615b89aa09b567ccefe54b6304ef10 Mon Sep 17 00:00:00 2001 From: Nicholas Date: Wed, 24 Jan 2024 19:48:47 +0100 Subject: [PATCH] feature: add a simple honeypot implementation that denies the qr code creation when the field is set (#183) --- lib/qrstorage/qr_codes/qr_code.ex | 5 +++- .../templates/qr_code/form_audio.html.heex | 1 + .../templates/qr_code/form_link.html.heex | 1 + .../templates/qr_code/form_text.html.heex | 1 + .../qr_code/partials/_honeypot.html.heex | 1 + test/qrstorage/qr_codes_test.exs | 25 ++++++++++++++++--- .../controllers/qr_code_controller_test.exs | 11 +++++++- 7 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 lib/qrstorage_web/templates/qr_code/partials/_honeypot.html.heex diff --git a/lib/qrstorage/qr_codes/qr_code.ex b/lib/qrstorage/qr_codes/qr_code.ex index 37540f3..91c95c2 100644 --- a/lib/qrstorage/qr_codes/qr_code.ex +++ b/lib/qrstorage/qr_codes/qr_code.ex @@ -36,6 +36,7 @@ defmodule Qrstorage.QrCodes.QrCode do field :admin_url_id, :binary_id, read_after_writes: true field :dots_type, Ecto.Enum, values: @dots_types field :voice, Ecto.Enum, values: @voices + field :hp, :string, virtual: true timestamps() end @@ -52,8 +53,10 @@ defmodule Qrstorage.QrCodes.QrCode do :content_type, :deltas, :dots_type, - :voice + :voice, + :hp ]) + |> validate_length(:hp, is: 0) |> scrub_text |> validate_text_length(:text) |> validate_inclusion(:color, @colors) diff --git a/lib/qrstorage_web/templates/qr_code/form_audio.html.heex b/lib/qrstorage_web/templates/qr_code/form_audio.html.heex index 8e9c696..6e87872 100644 --- a/lib/qrstorage_web/templates/qr_code/form_audio.html.heex +++ b/lib/qrstorage_web/templates/qr_code/form_audio.html.heex @@ -6,6 +6,7 @@ checked: true ) %> <%= select(f, :color, translated_colors_for_select(), class: "visually-hidden") %> + <%= render("partials/_honeypot.html", f: f) %> <%= if @changeset.action do %>
diff --git a/lib/qrstorage_web/templates/qr_code/form_link.html.heex b/lib/qrstorage_web/templates/qr_code/form_link.html.heex index 26ad2a0..7b3229e 100644 --- a/lib/qrstorage_web/templates/qr_code/form_link.html.heex +++ b/lib/qrstorage_web/templates/qr_code/form_link.html.heex @@ -6,6 +6,7 @@ checked: true ) %> <%= select(f, :color, translated_colors_for_select(), class: "visually-hidden") %> + <%= render("partials/_honeypot.html", f: f) %> <%= if @changeset.action do %>
diff --git a/lib/qrstorage_web/templates/qr_code/form_text.html.heex b/lib/qrstorage_web/templates/qr_code/form_text.html.heex index 3a1e87c..d8cc5ef 100644 --- a/lib/qrstorage_web/templates/qr_code/form_text.html.heex +++ b/lib/qrstorage_web/templates/qr_code/form_text.html.heex @@ -6,6 +6,7 @@ checked: true ) %> <%= select(f, :color, translated_colors_for_select(), class: "visually-hidden") %> + <%= render("partials/_honeypot.html", f: f) %> <%= textarea(f, :deltas, value: deltas_json_from_changeset(@changeset), diff --git a/lib/qrstorage_web/templates/qr_code/partials/_honeypot.html.heex b/lib/qrstorage_web/templates/qr_code/partials/_honeypot.html.heex new file mode 100644 index 0000000..6d24755 --- /dev/null +++ b/lib/qrstorage_web/templates/qr_code/partials/_honeypot.html.heex @@ -0,0 +1 @@ +<%= text_input(@f, :hp, class: "d-none") %> diff --git a/test/qrstorage/qr_codes_test.exs b/test/qrstorage/qr_codes_test.exs index 9b32a67..af17fff 100644 --- a/test/qrstorage/qr_codes_test.exs +++ b/test/qrstorage/qr_codes_test.exs @@ -12,6 +12,7 @@ defmodule Qrstorage.QrCodesTest do hide_text: false, content_type: "text", language: nil, + hp: nil, deltas: %{"id" => "test"}, dots_type: "dots" } @@ -22,6 +23,7 @@ defmodule Qrstorage.QrCodesTest do hide_text: false, content_type: "audio", language: "de", + hp: nil, voice: "female", dots_type: "dots" } @@ -189,13 +191,30 @@ defmodule Qrstorage.QrCodesTest do end test "create_qr_code/1 with invalid dots_type returns error changeset" do - invalid_link_attrs = %{@valid_attrs | dots_type: "invalid"} - assert {:error, %Ecto.Changeset{}} = QrCodes.create_qr_code(invalid_link_attrs) + invalid_attrs = %{@valid_attrs | dots_type: "invalid"} + assert {:error, %Ecto.Changeset{}} = QrCodes.create_qr_code(invalid_attrs) end test "create_qr_code/1 without dots_type returns error changeset" do - invalid_link_attrs = %{@valid_attrs | dots_type: ""} + invalid_attrs = %{@valid_attrs | dots_type: ""} + assert {:error, %Ecto.Changeset{}} = QrCodes.create_qr_code(invalid_attrs) + end + end + + describe "honeypot" do + test "create_qr_code/1 with audio type and honeypot set returns error changeset" do + invalid_audio_attrs = %{@valid_audio_attrs | hp: "set"} + assert {:error, %Ecto.Changeset{}} = QrCodes.create_qr_code(invalid_audio_attrs) + end + + test "create_qr_code/1 with link type and honeypot set returns error changeset" do + invalid_link_attrs = %{@valid_attrs | content_type: "link", hp: "set"} assert {:error, %Ecto.Changeset{}} = QrCodes.create_qr_code(invalid_link_attrs) end + + test "create_qr_code/1 with text type and honeypot set returns error changeset" do + invalid_text_attrs = %{@valid_attrs | hp: "set"} + assert {:error, %Ecto.Changeset{}} = QrCodes.create_qr_code(invalid_text_attrs) + end end end diff --git a/test/qrstorage_web/controllers/qr_code_controller_test.exs b/test/qrstorage_web/controllers/qr_code_controller_test.exs index 01892da..327e2cc 100644 --- a/test/qrstorage_web/controllers/qr_code_controller_test.exs +++ b/test/qrstorage_web/controllers/qr_code_controller_test.exs @@ -14,7 +14,8 @@ defmodule QrstorageWeb.QrCodeControllerTest do language: nil, content_type: "text", dots_type: "dots", - voice: nil + voice: nil, + hp: nil } @invalid_attrs %{delete_after: "10", text: nil, language: nil, content_type: "text"} @@ -90,6 +91,14 @@ defmodule QrstorageWeb.QrCodeControllerTest do end end + describe "honeypot create qr_code" do + test "does not create qr_code when honeypot is set", %{conn: conn} do + honeypot_set_attrs = %{@create_attrs | hp: "set"} + conn = post(conn, Routes.qr_code_path(conn, :create), qr_code: honeypot_set_attrs) + assert html_response(conn, 200) =~ "Oops, something went wrong" + end + end + describe "create audio qr_code" do test "audio code is successfully created", %{conn: conn} do Qrstorage.Services.Gcp.GoogleApiServiceMock