diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/pleroma/captcha/captcha.ex | 29 | ||||
-rw-r--r-- | lib/pleroma/captcha/captcha_service.ex | 12 | ||||
-rw-r--r-- | lib/pleroma/captcha/kocaptcha.ex | 82 | ||||
-rw-r--r-- | lib/pleroma/web/twitter_api/twitter_api.ex | 16 |
4 files changed, 65 insertions, 74 deletions
diff --git a/lib/pleroma/captcha/captcha.ex b/lib/pleroma/captcha/captcha.ex index 5630f6b57..61a0f907f 100644 --- a/lib/pleroma/captcha/captcha.ex +++ b/lib/pleroma/captcha/captcha.ex @@ -1,8 +1,6 @@ defmodule Pleroma.Captcha do use GenServer - @ets_options [:ordered_set, :private, :named_table, {:read_concurrency, true}] - @doc false def start_link() do GenServer.start_link(__MODULE__, [], name: __MODULE__) @@ -10,14 +8,6 @@ defmodule Pleroma.Captcha do @doc false def init(_) do - # Create a ETS table to store captchas - ets_name = Module.concat(method(), Ets) - ^ets_name = :ets.new(Module.concat(method(), Ets), @ets_options) - - # Clean up old captchas every few minutes - seconds_retained = Pleroma.Config.get!([__MODULE__, :seconds_retained]) - Process.send_after(self(), :cleanup, 1000 * seconds_retained) - {:ok, nil} end @@ -31,8 +21,8 @@ defmodule Pleroma.Captcha do @doc """ Ask the configured captcha service to validate the captcha """ - def validate(token, captcha) do - GenServer.call(__MODULE__, {:validate, token, captcha}) + def validate(token, captcha, answer_data) do + GenServer.call(__MODULE__, {:validate, token, captcha, answer_data}) end @doc false @@ -47,19 +37,8 @@ defmodule Pleroma.Captcha do end @doc false - def handle_call({:validate, token, captcha}, _from, state) do - {:reply, method().validate(token, captcha), state} - end - - @doc false - def handle_info(:cleanup, state) do - :ok = method().cleanup() - - seconds_retained = Pleroma.Config.get!([__MODULE__, :seconds_retained]) - # Schedule the next clenup - Process.send_after(self(), :cleanup, 1000 * seconds_retained) - - {:noreply, state} + def handle_call({:validate, token, captcha, answer_data}, _from, state) do + {:reply, method().validate(token, captcha, answer_data), state} end defp method, do: Pleroma.Config.get!([__MODULE__, :method]) diff --git a/lib/pleroma/captcha/captcha_service.ex b/lib/pleroma/captcha/captcha_service.ex index 8d0b76f86..6f36d29b0 100644 --- a/lib/pleroma/captcha/captcha_service.ex +++ b/lib/pleroma/captcha/captcha_service.ex @@ -14,15 +14,15 @@ defmodule Pleroma.Captcha.Service do Arguments: * `token` the captcha is associated with * `captcha` solution of the captcha to validate + * `answer_data` is the data needed to validate the answer (presumably encrypted) Returns: `true` if captcha is valid, `false` if not """ - @callback validate(token :: String.t(), captcha :: String.t()) :: boolean - - @doc """ - This function is called periodically to clean up old captchas - """ - @callback cleanup() :: :ok + @callback validate( + token :: String.t(), + captcha :: String.t(), + answer_data :: String.t() + ) :: :ok | {:error, String.t()} end diff --git a/lib/pleroma/captcha/kocaptcha.ex b/lib/pleroma/captcha/kocaptcha.ex index 51900d123..f881c7b65 100644 --- a/lib/pleroma/captcha/kocaptcha.ex +++ b/lib/pleroma/captcha/kocaptcha.ex @@ -1,11 +1,11 @@ defmodule Pleroma.Captcha.Kocaptcha do + alias Plug.Crypto.KeyGenerator + alias Plug.Crypto.MessageEncryptor alias Calendar.DateTime alias Pleroma.Captcha.Service @behaviour Service - @ets __MODULE__.Ets - @impl Service def new() do endpoint = Pleroma.Config.get!([__MODULE__, :endpoint]) @@ -18,50 +18,56 @@ defmodule Pleroma.Captcha.Kocaptcha do json_resp = Poison.decode!(res.body) token = json_resp["token"] + answer_md5 = json_resp["md5"] - true = - :ets.insert( - @ets, - {token, json_resp["md5"], DateTime.now_utc() |> DateTime.Format.unix()} - ) + secret_key_base = Pleroma.Config.get!([Pleroma.Web.Endpoint, :secret_key_base]) - %{type: :kocaptcha, token: token, url: endpoint <> json_resp["url"]} - end - end - - @impl Service - def validate(token, captcha) 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) + # This make salt a little different for two keys + secret = KeyGenerator.generate(secret_key_base, token <> "_encrypt") + sign_secret = KeyGenerator.generate(secret_key_base, token <> "_sign") + # Basicallty copy what Phoenix.Token does here, add the time to + # the actual data and make it a binary to then encrypt it + encrypted_captcha_answer = + %{ + at: DateTime.now_utc(), + answer_md5: answer_md5 + } + |> :erlang.term_to_binary() + |> MessageEncryptor.encrypt(secret, sign_secret) - true - else - _ -> false + %{ + type: :kocaptcha, + token: token, + url: endpoint <> json_resp["url"], + answer_data: encrypted_captcha_answer + } end end @impl Service - def cleanup() do - seconds_retained = Pleroma.Config.get!([Pleroma.Captcha, :seconds_retained]) - # If the time in ETS is less than current_time - seconds_retained, then the time has - # already passed - delete_after = - DateTime.subtract!(DateTime.now_utc(), seconds_retained) |> DateTime.Format.unix() + def validate(token, captcha, answer_data) do + secret_key_base = Pleroma.Config.get!([Pleroma.Web.Endpoint, :secret_key_base]) + secret = KeyGenerator.generate(secret_key_base, token <> "_encrypt") + sign_secret = KeyGenerator.generate(secret_key_base, token <> "_sign") - :ets.select_delete( - @ets, - [ - { - {:_, :_, :"$1"}, - [{:<, :"$1", {:const, delete_after}}], - [true] - } - ] - ) + # If the time found is less than (current_time - seconds_valid), then the time has already passed. + # Later we check that the time found is more than the presumed invalidatation time, that means + # that the data is still valid and the captcha can be checked + seconds_valid = Pleroma.Config.get!([Pleroma.Captcha, :seconds_valid]) + valid_if_after = DateTime.subtract!(DateTime.now_utc(), seconds_valid) - :ok + with {:ok, data} <- MessageEncryptor.decrypt(answer_data, secret, sign_secret), + %{at: at, answer_md5: answer_md5} <- :erlang.binary_to_term(data) do + if DateTime.after?(at, valid_if_after) do + if not is_nil(captcha) and + :crypto.hash(:md5, captcha) |> Base.encode16() == String.upcase(answer_md5), + do: :ok, + else: {:error, "Invalid CAPTCHA"} + else + {:error, "CAPTCHA expired"} + end + else + _ -> {:error, "Invalid answer data"} + end end end diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index d816dc3bc..9e15f2c33 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -136,22 +136,28 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do password: params["password"], password_confirmation: params["confirm"], captcha_solution: params["captcha_solution"], - captcha_token: params["captcha_token"] + captcha_token: params["captcha_token"], + captcha_answer_data: params["captcha_answer_data"] } captcha_enabled = Pleroma.Config.get([Pleroma.Captcha, :enabled]) # true if captcha is disabled or enabled and valid, false otherwise captcha_ok = if !captcha_enabled do - true + :ok else - Pleroma.Captcha.validate(params[:captcha_token], params[:captcha_solution]) + Pleroma.Captcha.validate( + params[:captcha_token], + params[:captcha_solution], + params[:captcha_answer_data] + ) end # Captcha invalid - if not captcha_ok do + if captcha_ok != :ok do + {:error, error} = captcha_ok # I have no idea how this error handling works - {:error, %{error: Jason.encode!(%{captcha: ["Invalid CAPTCHA"]})}} + {:error, %{error: Jason.encode!(%{captcha: [error]})}} else registrations_open = Pleroma.Config.get([:instance, :registrations_open]) |