From 3dfc9115a1f9b594ae8a4aea9c6e58a025c8aa9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= Date: Wed, 3 Feb 2021 10:12:23 +0100 Subject: [PATCH] Add captcha to registrations (fixes #9) --- lib/recycledcloud/accounts/user.ex | 14 +++++++++++++- lib/recycledcloud/captcha.ex | 17 +++++++++-------- .../user_registration_controller.ex | 18 ++++++++++++++---- .../templates/support/new.html.eex | 2 +- .../templates/user_registration/new.html.eex | 12 ++++++++++++ test/recycledcloud/accounts_test.exs | 5 +++-- 6 files changed, 52 insertions(+), 16 deletions(-) diff --git a/lib/recycledcloud/accounts/user.ex b/lib/recycledcloud/accounts/user.ex index 8ff984b..bf2fb6a 100644 --- a/lib/recycledcloud/accounts/user.ex +++ b/lib/recycledcloud/accounts/user.ex @@ -17,6 +17,7 @@ defmodule RecycledCloud.Accounts.User do field :email, :string, virtual: true field :confirmed_at, :naive_datetime field :partner_id, :integer + field :captcha, :integer, virtual: :true has_many :keys, Accounts.Key @@ -151,8 +152,13 @@ defmodule RecycledCloud.Accounts.User do also be very expensive to hash for certain algorithms. """ def registration_changeset(user, attrs) do + expected_captcha_result = attrs |> Map.get("expected_captcha") + user - |> insertion_changeset(attrs) + |> cast(attrs, [:username, :password, :email, :dn, :captcha]) + |> RecycledCloud.Captcha.validate(expected_captcha_result) + |> validate_username() + |> validate_email() |> validate_password() end @@ -160,6 +166,7 @@ defmodule RecycledCloud.Accounts.User do user |> cast(attrs, [:username, :password, :email, :dn]) |> validate_email() + |> validate_username() end defp validate_email(changeset) do @@ -178,6 +185,11 @@ defmodule RecycledCloud.Accounts.User do #|> validate_format(:password, ~r/[!?@#$%^&*_0-9]/, message: "at least one digit or punctuation character") end + defp validate_username(changeset) do + changeset + |> validate_required([:username]) + end + @doc """ A user changeset for changing the email. diff --git a/lib/recycledcloud/captcha.ex b/lib/recycledcloud/captcha.ex index 265f15e..d45f309 100644 --- a/lib/recycledcloud/captcha.ex +++ b/lib/recycledcloud/captcha.ex @@ -3,9 +3,13 @@ defmodule RecycledCloud.Captcha do # https://hex.pm/packages/captcha since it would break accessibility for # blind people. Let's make it simple maths instead! + # Captcha expression tuning: + @terms 1..6 + @operands ["+", "-", "*"] + def validate(changeset, expected_result) do - captcha_result = Ecto.Changeset.get_field(changeset, :captcha) - if captcha_result == expected_result do + result = Ecto.Changeset.get_field(changeset, :captcha) + if result == expected_result do changeset else Ecto.Changeset.add_error(changeset, :captcha, "does not match") @@ -13,12 +17,9 @@ defmodule RecycledCloud.Captcha do end def generate do - terms = 1..6 - operands = ["+", "-", "*"] - - left = terms |> Enum.random - right = terms |> Enum.random - operation = operands |> Enum.random + left = @terms |> Enum.random + right = @terms |> Enum.random + operation = @operands |> Enum.random text = "#{left} #{operation} #{right}" value = case operation do diff --git a/lib/recycledcloud_web/controllers/user_registration_controller.ex b/lib/recycledcloud_web/controllers/user_registration_controller.ex index f8bba99..3b6f271 100644 --- a/lib/recycledcloud_web/controllers/user_registration_controller.ex +++ b/lib/recycledcloud_web/controllers/user_registration_controller.ex @@ -1,6 +1,7 @@ defmodule RecycledCloudWeb.UserRegistrationController do use RecycledCloudWeb, :controller + alias RecycledCloud.Captcha alias RecycledCloud.Accounts alias RecycledCloud.Accounts.User alias RecycledCloudWeb.UserAuth @@ -11,6 +12,7 @@ defmodule RecycledCloudWeb.UserRegistrationController do def new(conn, _params) do changeset = Accounts.change_user_registration(%User{}) + {captcha_text, captcha_result} = Captcha.generate() with_notice = unless is_registration_enabled?() do conn @@ -20,17 +22,20 @@ defmodule RecycledCloudWeb.UserRegistrationController do end with_notice + |> assign(:captcha, captcha_text) + |> put_session(:captcha, captcha_result) |> render("new.html", changeset: changeset) end def create(conn, %{"user" => user_params}) do - changeset = Accounts.change_user_registration(%User{}) - unless is_registration_enabled?() do conn |> redirect(to: Routes.user_registration_path(conn, :new)) else - case Accounts.register_user(user_params) do + attrs = user_params + |> Map.put("expected_captcha", get_session(conn, :captcha)) + + case Accounts.register_user(attrs) do {:ok, user} -> {:ok, _} = Accounts.deliver_user_confirmation_instructions( @@ -43,7 +48,12 @@ defmodule RecycledCloudWeb.UserRegistrationController do |> UserAuth.log_in_user(user) {:error, %Ecto.Changeset{} = changeset} -> - render(conn, "new.html", changeset: changeset) + {captcha_text, captcha_result} = Captcha.generate() + + conn + |> assign(:captcha, captcha_text) + |> put_session(:captcha, captcha_result) + |> render("new.html", changeset: changeset) end end end diff --git a/lib/recycledcloud_web/templates/support/new.html.eex b/lib/recycledcloud_web/templates/support/new.html.eex index 6d50ef9..49a3d54 100644 --- a/lib/recycledcloud_web/templates/support/new.html.eex +++ b/lib/recycledcloud_web/templates/support/new.html.eex @@ -23,7 +23,7 @@

- Can you answer the following expression to confirm that you are not basic + Can you answer the following expression to confirm you are not basic robot?
diff --git a/lib/recycledcloud_web/templates/user_registration/new.html.eex b/lib/recycledcloud_web/templates/user_registration/new.html.eex index 74c2cfa..f92db18 100644 --- a/lib/recycledcloud_web/templates/user_registration/new.html.eex +++ b/lib/recycledcloud_web/templates/user_registration/new.html.eex @@ -22,6 +22,18 @@
+

+ Can you answer the following expression to confirm you are not basic robot? + +
+ + <%= @captcha %> = + + <%= text_input f, :captcha, placeholder: "Captcha" %> + <%= error_tag f, :captcha %> + +

+
<%= submit "Register" %>
diff --git a/test/recycledcloud/accounts_test.exs b/test/recycledcloud/accounts_test.exs index 857447e..22d4065 100644 --- a/test/recycledcloud/accounts_test.exs +++ b/test/recycledcloud/accounts_test.exs @@ -95,15 +95,16 @@ defmodule RecycledCloud.AccountsTest do describe "change_user_registration/2" do test "returns a changeset" do assert %Ecto.Changeset{} = changeset = Accounts.change_user_registration(%User{}) - assert changeset.required == [:password, :email] + assert changeset.required == [:password, :email, :username] end test "allows fields to be set" do + username = unique_user_name() email = unique_user_email() password = valid_user_password() changeset = - Accounts.change_user_registration(%User{}, %{"email" => email, "password" => password}) + Accounts.change_user_registration(%User{}, %{"username" => username, "email" => email, "password" => password}) assert changeset.valid? assert get_change(changeset, :email) == email