Add base CAPTCHA support (currently only kocaptcha)
authorEkaterina Vaartis <vaartis@cock.li>
Fri, 14 Dec 2018 22:31:19 +0000 (01:31 +0300)
committerEkaterina Vaartis <vaartis@cock.li>
Sat, 15 Dec 2018 00:12:44 +0000 (03:12 +0300)
config/config.exs
lib/pleroma/application.ex
lib/pleroma/captcha.ex [new file with mode: 0644]
lib/pleroma/web/router.ex
lib/pleroma/web/twitter_api/controllers/util_controller.ex
lib/pleroma/web/twitter_api/twitter_api.ex

index 1401b0a3dadabe90c276c04b97df163aee154c11..df4c618a75f33e531c241a6a64705aa5e4f59290 100644 (file)
@@ -10,6 +10,13 @@ config :pleroma, ecto_repos: [Pleroma.Repo]
 
 config :pleroma, Pleroma.Repo, types: Pleroma.PostgresTypes
 
+config :pleroma, Pleroma.Captcha,
+  method: Pleroma.Captcha.Kocaptcha
+
+# Kocaptcha is a very simple captcha service, the source code is here: https://github.com/koto-bank/kocaptcha
+config :pleroma, Pleroma.Captcha.Kocaptcha,
+  endpoint: "http://localhost:9093"
+
 # Upload configuration
 config :pleroma, Pleroma.Upload,
   uploader: Pleroma.Uploaders.Local,
index 8705395a4faf2c6199c3a83f3367a89bfd1cd1e8..e1599195717d59fa5112674d89b58ee08a7cd51a 100644 (file)
@@ -24,6 +24,7 @@ defmodule Pleroma.Application do
         # Start the Ecto repository
         supervisor(Pleroma.Repo, []),
         worker(Pleroma.Emoji, []),
+        worker(Pleroma.Captcha, []),
         worker(
           Cachex,
           [
diff --git a/lib/pleroma/captcha.ex b/lib/pleroma/captcha.ex
new file mode 100644 (file)
index 0000000..31f3bc7
--- /dev/null
@@ -0,0 +1,68 @@
+defmodule Pleroma.Captcha do
+  use GenServer
+
+  @ets __MODULE__.Ets
+  @ets_options [:ordered_set, :private, :named_table, {:read_concurrency, true}]
+
+
+  @doc false
+  def start_link() do
+    GenServer.start_link(__MODULE__, [], name: __MODULE__)
+  end
+
+
+  @doc false
+  def init(_) do
+    @ets = :ets.new(@ets, @ets_options)
+
+    {:ok, nil}
+  end
+
+  def new() do
+    GenServer.call(__MODULE__, :new)
+  end
+
+  def validate(token, captcha) do
+    GenServer.call(__MODULE__, {:validate, token, captcha})
+  end
+
+  @doc false
+  def handle_call(:new, _from, state) do
+    method = Pleroma.Config.get!([__MODULE__, :method])
+
+    case method do
+      __MODULE__.Kocaptcha ->
+        endpoint = Pleroma.Config.get!([method, :endpoint])
+        case HTTPoison.get(endpoint <> "/new") do
+          {:error, _} ->
+            %{error: "Kocaptcha service unavailable"}
+          {:ok, res} ->
+            json_resp = Poison.decode!(res.body)
+
+            token = json_resp["token"]
+
+            true = :ets.insert(@ets, {token, json_resp["md5"]})
+
+            {
+              :reply,
+              %{type: :kocaptcha, token: token, url: endpoint <> json_resp["url"]},
+              state
+            }
+        end
+    end
+  end
+
+  @doc false
+  def handle_call({:validate, token, captcha}, _from, state) do
+    with false <- is_nil(captcha),
+         [{^token, saved_md5}] <- :ets.lookup(@ets, token),
+         true <- (:crypto.hash(:md5, captcha) |> Base.encode16) == String.upcase(saved_md5) do
+      # Clear the saved value
+      :ets.delete(@ets, token)
+
+      {:reply, true, state}
+    else
+      e -> IO.inspect(e); {:reply, false, state}
+    end
+  end
+end
index daff3362c9b9ae3a039883253e886706558e6961..60342cfb447644fc2609056b51b52b6ecaea633f 100644 (file)
@@ -99,6 +99,7 @@ defmodule Pleroma.Web.Router do
     get("/password_reset/:token", UtilController, :show_password_reset)
     post("/password_reset", UtilController, :password_reset)
     get("/emoji", UtilController, :emoji)
+    get("/captcha", UtilController, :captcha)
   end
 
   scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do
index 2f2b69623d63474bd48f2784ef4eb34703396ae5..38653f0b8737c203f0626381c15670d4f38a33fa 100644 (file)
@@ -284,4 +284,8 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
         json(conn, %{error: msg})
     end
   end
+
+  def captcha(conn, _params) do
+    json(conn, Pleroma.Captcha.new())
+  end
 end
index 1e764f24aef62496f309e1ea9b4cace5a3dc9c17..c9e8fbcbb4d0825abb3ff0ad60f854786406903d 100644 (file)
@@ -132,38 +132,47 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
       bio: User.parse_bio(params["bio"]),
       email: params["email"],
       password: params["password"],
-      password_confirmation: params["confirm"]
+      password_confirmation: params["confirm"],
+      captcha_solution: params["captcha_solution"],
+      captcha_token: params["captcha_token"]
     }
 
-    registrations_open = Pleroma.Config.get([:instance, :registrations_open])
+    # Captcha invalid
+    if not Pleroma.Captcha.validate(params[:captcha_token], params[:captcha_solution]) do
+      # I have no idea how this error handling works
+      {:error, %{error: Jason.encode!(%{captcha: ["Invalid CAPTCHA"]})}}
+    else
+      registrations_open = Pleroma.Config.get([:instance, :registrations_open])
 
-    # no need to query DB if registration is open
-    token =
-      unless registrations_open || is_nil(tokenString) do
+      # no need to query DB if registration is open
+      token =
+        unless registrations_open || is_nil(tokenString) do
         Repo.get_by(UserInviteToken, %{token: tokenString})
       end
 
-    cond do
-      registrations_open || (!is_nil(token) && !token.used) ->
-        changeset = User.register_changeset(%User{info: %{}}, params)
+      cond do
+        registrations_open || (!is_nil(token) && !token.used) ->
+          changeset = User.register_changeset(%User{info: %{}}, params)
 
-        with {:ok, user} <- Repo.insert(changeset) do
-          !registrations_open && UserInviteToken.mark_as_used(token.token)
-          {:ok, user}
-        else
-          {:error, changeset} ->
-            errors =
+          with {:ok, user} <- Repo.insert(changeset) do
+            !registrations_open && UserInviteToken.mark_as_used(token.token)
+            {:ok, user}
+          else
+            {:error, changeset} ->
+              errors =
               Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end)
               |> Jason.encode!()
 
             {:error, %{error: errors}}
-        end
+          end
+
 
-      !registrations_open && is_nil(token) ->
-        {:error, "Invalid token"}
+        !registrations_open && is_nil(token) ->
+            {:error, "Invalid token"}
 
-      !registrations_open && token.used ->
-        {:error, "Expired token"}
+        !registrations_open && token.used ->
+            {:error, "Expired token"}
+      end
     end
   end