From a2399c1c7c17ee1c8e85ae0b6095405c7cb9f6f1 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sat, 15 Dec 2018 01:31:19 +0300 Subject: [PATCH 01/18] Add base CAPTCHA support (currently only kocaptcha) --- config/config.exs | 7 ++ lib/pleroma/application.ex | 1 + lib/pleroma/captcha.ex | 68 +++++++++++++++++++ lib/pleroma/web/router.ex | 1 + .../controllers/util_controller.ex | 4 ++ lib/pleroma/web/twitter_api/twitter_api.ex | 47 +++++++------ 6 files changed, 109 insertions(+), 19 deletions(-) create mode 100644 lib/pleroma/captcha.ex diff --git a/config/config.exs b/config/config.exs index 1401b0a3d..df4c618a7 100644 --- a/config/config.exs +++ b/config/config.exs @@ -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, diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 8705395a4..e15991957 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -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 index 000000000..31f3bc797 --- /dev/null +++ b/lib/pleroma/captcha.ex @@ -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 diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index daff3362c..60342cfb4 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -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 diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 2f2b69623..38653f0b8 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -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 diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 1e764f24a..c9e8fbcbb 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -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 && token.used -> - {:error, "Expired token"} + !registrations_open && is_nil(token) -> + {:error, "Invalid token"} + + !registrations_open && token.used -> + {:error, "Expired token"} + end end end From 28c43a417e89ad68674f6e60d7d3025fbb4655ff Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sat, 15 Dec 2018 02:00:00 +0300 Subject: [PATCH 02/18] Add an ability to disabled captcha --- config/config.exs | 1 + lib/pleroma/captcha.ex | 44 +++++++++++++--------- lib/pleroma/web/twitter_api/twitter_api.ex | 10 ++++- 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/config/config.exs b/config/config.exs index df4c618a7..32593045c 100644 --- a/config/config.exs +++ b/config/config.exs @@ -11,6 +11,7 @@ config :pleroma, ecto_repos: [Pleroma.Repo] config :pleroma, Pleroma.Repo, types: Pleroma.PostgresTypes config :pleroma, Pleroma.Captcha, + enabled: false, method: Pleroma.Captcha.Kocaptcha # Kocaptcha is a very simple captcha service, the source code is here: https://github.com/koto-bank/kocaptcha diff --git a/lib/pleroma/captcha.ex b/lib/pleroma/captcha.ex index 31f3bc797..ffa5640ea 100644 --- a/lib/pleroma/captcha.ex +++ b/lib/pleroma/captcha.ex @@ -28,27 +28,37 @@ defmodule Pleroma.Captcha do @doc false def handle_call(:new, _from, state) do - method = Pleroma.Config.get!([__MODULE__, :method]) + enabled = Pleroma.Config.get([__MODULE__, :enabled]) - 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) + if !enabled do + { + :reply, + %{type: :none}, + state + } + else + method = Pleroma.Config.get!([__MODULE__, :method]) - token = json_resp["token"] + 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) - true = :ets.insert(@ets, {token, json_resp["md5"]}) + token = json_resp["token"] - { - :reply, - %{type: :kocaptcha, token: token, url: endpoint <> json_resp["url"]}, - state - } - end + true = :ets.insert(@ets, {token, json_resp["md5"]}) + + { + :reply, + %{type: :kocaptcha, token: token, url: endpoint <> json_resp["url"]}, + state + } + end + end end end diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index c9e8fbcbb..9f98c43c9 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -137,8 +137,16 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do captcha_token: params["captcha_token"] } + 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 + else + Pleroma.Captcha.validate(params[:captcha_token], params[:captcha_solution]) + end + # Captcha invalid - if not Pleroma.Captcha.validate(params[:captcha_token], params[:captcha_solution]) do + if not captcha_ok do # I have no idea how this error handling works {:error, %{error: Jason.encode!(%{captcha: ["Invalid CAPTCHA"]})}} else From b5518da90468ab1cde40593695d75f3d72d66783 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sat, 15 Dec 2018 22:06:44 +0300 Subject: [PATCH 03/18] Separate captcha implementation into a behaviour and use it --- lib/pleroma/captcha.ex | 78 -------------------------- lib/pleroma/captcha/captcha.ex | 51 +++++++++++++++++ lib/pleroma/captcha/captcha_service.ex | 24 ++++++++ lib/pleroma/captcha/kocaptcha.ex | 37 ++++++++++++ 4 files changed, 112 insertions(+), 78 deletions(-) delete mode 100644 lib/pleroma/captcha.ex create mode 100644 lib/pleroma/captcha/captcha.ex create mode 100644 lib/pleroma/captcha/captcha_service.ex create mode 100644 lib/pleroma/captcha/kocaptcha.ex diff --git a/lib/pleroma/captcha.ex b/lib/pleroma/captcha.ex deleted file mode 100644 index ffa5640ea..000000000 --- a/lib/pleroma/captcha.ex +++ /dev/null @@ -1,78 +0,0 @@ -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 - enabled = Pleroma.Config.get([__MODULE__, :enabled]) - - if !enabled do - { - :reply, - %{type: :none}, - state - } - else - 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 - 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 diff --git a/lib/pleroma/captcha/captcha.ex b/lib/pleroma/captcha/captcha.ex new file mode 100644 index 000000000..df33406ee --- /dev/null +++ b/lib/pleroma/captcha/captcha.ex @@ -0,0 +1,51 @@ +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__) + end + + @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) + + {:ok, nil} + end + + @doc """ + Ask the configured captcha service for a new captcha + """ + def new() do + GenServer.call(__MODULE__, :new) + end + + @doc """ + Ask the configured captcha service to validate the captcha + """ + def validate(token, captcha) do + GenServer.call(__MODULE__, {:validate, token, captcha}) + end + + @doc false + def handle_call(:new, _from, state) do + enabled = Pleroma.Config.get([__MODULE__, :enabled]) + + if !enabled do + {:reply, %{type: :none}, state} + else + {:reply, method().new(), state} + end + end + + @doc false + def handle_call({:validate, token, captcha}, _from, state) do + {:reply, method().validate(token, captcha), state} + end + + defp method, do: Pleroma.Config.get!([__MODULE__, :method]) +end diff --git a/lib/pleroma/captcha/captcha_service.ex b/lib/pleroma/captcha/captcha_service.ex new file mode 100644 index 000000000..ae1d6e7c7 --- /dev/null +++ b/lib/pleroma/captcha/captcha_service.ex @@ -0,0 +1,24 @@ +defmodule Pleroma.Captcha.Service do + + @doc """ + Request new captcha from a captcha service. + + Returns: + + Service-specific data for using the newly created captcha + """ + @callback new() :: map + + @doc """ + Validated the provided captcha solution. + + Arguments: + * `token` the captcha is associated with + * `captcha` solution of the captcha to validate + + Returns: + + `true` if captcha is valid, `false` if not + """ + @callback validate(token :: String.t, captcha :: String.t) :: boolean +end diff --git a/lib/pleroma/captcha/kocaptcha.ex b/lib/pleroma/captcha/kocaptcha.ex new file mode 100644 index 000000000..abccbf6d3 --- /dev/null +++ b/lib/pleroma/captcha/kocaptcha.ex @@ -0,0 +1,37 @@ +defmodule Pleroma.Captcha.Kocaptcha do + alias Pleroma.Captcha.Service + @behaviour Service + + @ets __MODULE__.Ets + + @impl Service + def new() do + endpoint = Pleroma.Config.get!([__MODULE__, :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"]}) + + %{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) + + true + else + _ -> false + end + end +end From 23549d39521386f217a57ef1aeb3d660eff06860 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sat, 15 Dec 2018 22:08:26 +0300 Subject: [PATCH 04/18] Formatting fixes --- config/config.exs | 3 +-- lib/pleroma/captcha/captcha_service.ex | 3 +-- lib/pleroma/captcha/kocaptcha.ex | 4 +++- lib/pleroma/web/twitter_api/twitter_api.ex | 26 +++++++++++----------- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/config/config.exs b/config/config.exs index 32593045c..b7d439e9d 100644 --- a/config/config.exs +++ b/config/config.exs @@ -15,8 +15,7 @@ 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" +config :pleroma, Pleroma.Captcha.Kocaptcha, endpoint: "http://localhost:9093" # Upload configuration config :pleroma, Pleroma.Upload, diff --git a/lib/pleroma/captcha/captcha_service.ex b/lib/pleroma/captcha/captcha_service.ex index ae1d6e7c7..907a73ad0 100644 --- a/lib/pleroma/captcha/captcha_service.ex +++ b/lib/pleroma/captcha/captcha_service.ex @@ -1,5 +1,4 @@ defmodule Pleroma.Captcha.Service do - @doc """ Request new captcha from a captcha service. @@ -20,5 +19,5 @@ defmodule Pleroma.Captcha.Service do `true` if captcha is valid, `false` if not """ - @callback validate(token :: String.t, captcha :: String.t) :: boolean + @callback validate(token :: String.t(), captcha :: String.t()) :: boolean end diff --git a/lib/pleroma/captcha/kocaptcha.ex b/lib/pleroma/captcha/kocaptcha.ex index abccbf6d3..173ce17f7 100644 --- a/lib/pleroma/captcha/kocaptcha.ex +++ b/lib/pleroma/captcha/kocaptcha.ex @@ -7,9 +7,11 @@ defmodule Pleroma.Captcha.Kocaptcha do @impl Service def new() do endpoint = Pleroma.Config.get!([__MODULE__, :endpoint]) + case HTTPoison.get(endpoint <> "/new") do {:error, _} -> %{error: "Kocaptcha service unavailable"} + {:ok, res} -> json_resp = Poison.decode!(res.body) @@ -25,7 +27,7 @@ defmodule Pleroma.Captcha.Kocaptcha do 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 + true <- :crypto.hash(:md5, captcha) |> Base.encode16() == String.upcase(saved_md5) do # Clear the saved value :ets.delete(@ets, token) diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 9f98c43c9..90b8345c5 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -139,11 +139,12 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do 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 - else - Pleroma.Captcha.validate(params[:captcha_token], params[:captcha_solution]) - end + captcha_ok = + if !captcha_enabled do + true + else + Pleroma.Captcha.validate(params[:captcha_token], params[:captcha_solution]) + end # Captcha invalid if not captcha_ok do @@ -155,8 +156,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI 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 + Repo.get_by(UserInviteToken, %{token: tokenString}) + end cond do registrations_open || (!is_nil(token) && !token.used) -> @@ -168,18 +169,17 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do else {:error, changeset} -> errors = - Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end) - |> Jason.encode!() + Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end) + |> Jason.encode!() - {:error, %{error: errors}} + {:error, %{error: errors}} end - !registrations_open && is_nil(token) -> - {:error, "Invalid token"} + {:error, "Invalid token"} !registrations_open && token.used -> - {:error, "Expired token"} + {:error, "Expired token"} end end end From 98e10c0d4f9d0aa32e34d706c9aa5919a64c2db2 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sat, 15 Dec 2018 22:12:20 +0300 Subject: [PATCH 05/18] Add captcha documentation to config.md --- config/config.exs | 1 - config/config.md | 12 +++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/config/config.exs b/config/config.exs index b7d439e9d..5149e9c41 100644 --- a/config/config.exs +++ b/config/config.exs @@ -14,7 +14,6 @@ config :pleroma, Pleroma.Captcha, enabled: false, 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 diff --git a/config/config.md b/config/config.md index 8282eab14..e8b5e52cb 100644 --- a/config/config.md +++ b/config/config.md @@ -7,7 +7,7 @@ If you run Pleroma with ``MIX_ENV=prod`` the file is ``prod.secret.exs``, otherw * `uploader`: Select which `Pleroma.Uploaders` to use * `filters`: List of `Pleroma.Upload.Filter` to use. * `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host. -* `proxy_remote`: If you're using a remote uploader, Pleroma will proxy media requests instead of redirecting to it. +* `proxy_remote`: If you\'re using a remote uploader, Pleroma will proxy media requests instead of redirecting to it. * `proxy_opts`: Proxy options, see `Pleroma.ReverseProxy` documentation. Note: `strip_exif` has been replaced by `Pleroma.Upload.Filter.Mogrify`. @@ -163,3 +163,13 @@ Web Push Notifications configuration. You can use the mix task `mix web_push.gen * ``subject``: a mailto link for the administrative contact. It’s best if this email is not a personal email address, but rather a group email so that if a person leaves an organization, is unavailable for an extended period, or otherwise can’t respond, someone else on the list can. * ``public_key``: VAPID public key * ``private_key``: VAPID private key + +## Pleroma.Captcha +* `enabled`: Whether the captcha should be shown on registration +* `method`: The method/service to use for captcha + +### Pleroma.Captcha.Kocaptcha +Kocaptcha is a very simple captcha service with a single API endpoint, +the source code is here: https://github.com/koto-bank/kocaptcha + +* `endpoint`: the kocaptcha endpoint to use \ No newline at end of file From e8537208bd9af701cbfc788ca307b8352306a36b Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sat, 15 Dec 2018 22:38:39 +0300 Subject: [PATCH 06/18] Add a captcha mock for tests --- config/test.exs | 5 +++++ test/support/captcha_mock.ex | 10 ++++++++++ 2 files changed, 15 insertions(+) create mode 100644 test/support/captcha_mock.ex diff --git a/config/test.exs b/config/test.exs index 5c6acfead..f5348bd46 100644 --- a/config/test.exs +++ b/config/test.exs @@ -6,6 +6,11 @@ config :pleroma, Pleroma.Web.Endpoint, http: [port: 4001], server: false +# Disable captha for tests +config :pleroma, Pleroma.Captcha, + enabled: true, + method: Pleroma.Captcha.Mock # A fake captcha service for tests + # Print only warnings and errors during test config :logger, level: :warn diff --git a/test/support/captcha_mock.ex b/test/support/captcha_mock.ex new file mode 100644 index 000000000..9d79f2e95 --- /dev/null +++ b/test/support/captcha_mock.ex @@ -0,0 +1,10 @@ +defmodule Pleroma.Captcha.Mock do + alias Pleroma.Captcha.Service + @behaviour Service + + @impl Service + def new(), do: %{type: :mock} + + @impl Service + def validate(_token, _captcha), do: true +end From 8d55a549e678daa057fce81d1d2ee46b2f8c5545 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sat, 15 Dec 2018 22:43:28 +0300 Subject: [PATCH 07/18] Replace HTTPoison with Tesla for kocaptha --- lib/pleroma/captcha/kocaptcha.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/captcha/kocaptcha.ex b/lib/pleroma/captcha/kocaptcha.ex index 173ce17f7..4ecd1a81f 100644 --- a/lib/pleroma/captcha/kocaptcha.ex +++ b/lib/pleroma/captcha/kocaptcha.ex @@ -8,7 +8,7 @@ defmodule Pleroma.Captcha.Kocaptcha do def new() do endpoint = Pleroma.Config.get!([__MODULE__, :endpoint]) - case HTTPoison.get(endpoint <> "/new") do + case Tesla.get(endpoint <> "/new") do {:error, _} -> %{error: "Kocaptcha service unavailable"} From 5f96c2d216c2728367dfdacb2dbbfc92eb30ce3c Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sat, 15 Dec 2018 23:38:19 +0300 Subject: [PATCH 08/18] Add a test for kocaptcha --- test/captcha_test.ex | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 test/captcha_test.ex diff --git a/test/captcha_test.ex b/test/captcha_test.ex new file mode 100644 index 000000000..3942cb051 --- /dev/null +++ b/test/captcha_test.ex @@ -0,0 +1,41 @@ +defmodule Pleroma.CaptchaTest do + use ExUnit.Case + + import Tesla.Mock + + @ets_options [:ordered_set, :private, :named_table, {:read_concurrency, true}] + + describe "Kocaptcha" do + + setup do + ets_name = Pleroma.Captcha.Kocaptcha.Ets + ^ets_name = :ets.new(ets_name, @ets_options) + + mock fn + %{method: :get, url: "http://localhost:9093/new"} -> + json( + %{ + md5: "63615261b77f5354fb8c4e4986477555", + token: "afa1815e14e29355e6c8f6b143a39fa2", + url: "/captchas/afa1815e14e29355e6c8f6b143a39fa2.png" + } + ) + end + + :ok + end + + test "new and validate" do + assert Pleroma.Captcha.Kocaptcha.new() == %{ + type: :kocaptcha, + token: "afa1815e14e29355e6c8f6b143a39fa2", + url: "http://localhost:9093/captchas/afa1815e14e29355e6c8f6b143a39fa2.png" + } + + assert Pleroma.Captcha.Kocaptcha.validate( + "afa1815e14e29355e6c8f6b143a39fa2", + "7oEy8c" + ) + end + end +end From c859cd1d61d81db4999c594ef61164d752d76145 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sat, 15 Dec 2018 23:39:23 +0300 Subject: [PATCH 09/18] Fix style --- config/test.exs | 3 ++- test/captcha_test.ex | 31 ++++++++++++++----------------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/config/test.exs b/config/test.exs index f5348bd46..5670e11a0 100644 --- a/config/test.exs +++ b/config/test.exs @@ -9,7 +9,8 @@ config :pleroma, Pleroma.Web.Endpoint, # Disable captha for tests config :pleroma, Pleroma.Captcha, enabled: true, - method: Pleroma.Captcha.Mock # A fake captcha service for tests + # A fake captcha service for tests + method: Pleroma.Captcha.Mock # Print only warnings and errors during test config :logger, level: :warn diff --git a/test/captcha_test.ex b/test/captcha_test.ex index 3942cb051..98e8da79b 100644 --- a/test/captcha_test.ex +++ b/test/captcha_test.ex @@ -6,36 +6,33 @@ defmodule Pleroma.CaptchaTest do @ets_options [:ordered_set, :private, :named_table, {:read_concurrency, true}] describe "Kocaptcha" do - setup do ets_name = Pleroma.Captcha.Kocaptcha.Ets ^ets_name = :ets.new(ets_name, @ets_options) - mock fn + mock(fn %{method: :get, url: "http://localhost:9093/new"} -> - json( - %{ - md5: "63615261b77f5354fb8c4e4986477555", - token: "afa1815e14e29355e6c8f6b143a39fa2", - url: "/captchas/afa1815e14e29355e6c8f6b143a39fa2.png" - } - ) - end + json(%{ + md5: "63615261b77f5354fb8c4e4986477555", + token: "afa1815e14e29355e6c8f6b143a39fa2", + url: "/captchas/afa1815e14e29355e6c8f6b143a39fa2.png" + }) + end) :ok end test "new and validate" do assert Pleroma.Captcha.Kocaptcha.new() == %{ - type: :kocaptcha, - token: "afa1815e14e29355e6c8f6b143a39fa2", - url: "http://localhost:9093/captchas/afa1815e14e29355e6c8f6b143a39fa2.png" - } + type: :kocaptcha, + token: "afa1815e14e29355e6c8f6b143a39fa2", + url: "http://localhost:9093/captchas/afa1815e14e29355e6c8f6b143a39fa2.png" + } assert Pleroma.Captcha.Kocaptcha.validate( - "afa1815e14e29355e6c8f6b143a39fa2", - "7oEy8c" - ) + "afa1815e14e29355e6c8f6b143a39fa2", + "7oEy8c" + ) end end end From 2e72d49e373abf2957df82db6f8031c62936f9ba Mon Sep 17 00:00:00 2001 From: vaartis Date: Sun, 16 Dec 2018 07:35:45 +0000 Subject: [PATCH 10/18] Rename captcha_test.ex to exs --- test/{captcha_test.ex => captcha_test.exs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{captcha_test.ex => captcha_test.exs} (100%) diff --git a/test/captcha_test.ex b/test/captcha_test.exs similarity index 100% rename from test/captcha_test.ex rename to test/captcha_test.exs From 6062885df6178c09544b6a0b5b731a554786397e Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sun, 16 Dec 2018 22:04:43 +0300 Subject: [PATCH 11/18] Add a configurable auto-cleanup for captchas --- config/config.exs | 1 + config/config.md | 1 + lib/pleroma/captcha/captcha.ex | 15 ++++++++++++++- lib/pleroma/captcha/captcha_service.ex | 5 +++++ lib/pleroma/captcha/kocaptcha.ex | 11 ++++++++++- 5 files changed, 31 insertions(+), 2 deletions(-) diff --git a/config/config.exs b/config/config.exs index 5149e9c41..21cdc0537 100644 --- a/config/config.exs +++ b/config/config.exs @@ -12,6 +12,7 @@ config :pleroma, Pleroma.Repo, types: Pleroma.PostgresTypes config :pleroma, Pleroma.Captcha, enabled: false, + minutes_retained: 5, method: Pleroma.Captcha.Kocaptcha config :pleroma, Pleroma.Captcha.Kocaptcha, endpoint: "http://localhost:9093" diff --git a/config/config.md b/config/config.md index e8b5e52cb..65f0866fd 100644 --- a/config/config.md +++ b/config/config.md @@ -167,6 +167,7 @@ Web Push Notifications configuration. You can use the mix task `mix web_push.gen ## Pleroma.Captcha * `enabled`: Whether the captcha should be shown on registration * `method`: The method/service to use for captcha +* `minutes_retained`: The time in minutes for which the captcha is valid (stored in the cache) ### Pleroma.Captcha.Kocaptcha Kocaptcha is a very simple captcha service with a single API endpoint, diff --git a/lib/pleroma/captcha/captcha.ex b/lib/pleroma/captcha/captcha.ex index df33406ee..2dcbc4717 100644 --- a/lib/pleroma/captcha/captcha.ex +++ b/lib/pleroma/captcha/captcha.ex @@ -38,7 +38,13 @@ defmodule Pleroma.Captcha do if !enabled do {:reply, %{type: :none}, state} else - {:reply, method().new(), state} + new_captcha = method().new() + + minutes_retained = Pleroma.Config.get!([__MODULE__, :minutes_retained]) + # Wait several minutes and if the captcha is still there, delete it + Process.send_after(self(), {:cleanup, new_captcha.token}, 1000 * 60 * minutes_retained) + + {:reply, new_captcha, state} end end @@ -47,5 +53,12 @@ defmodule Pleroma.Captcha do {:reply, method().validate(token, captcha), state} end + @doc false + def handle_info({:cleanup, token}, state) do + method().cleanup(token) + + {:noreply, state} + end + defp method, do: Pleroma.Config.get!([__MODULE__, :method]) end diff --git a/lib/pleroma/captcha/captcha_service.ex b/lib/pleroma/captcha/captcha_service.ex index 907a73ad0..fe5a6bf66 100644 --- a/lib/pleroma/captcha/captcha_service.ex +++ b/lib/pleroma/captcha/captcha_service.ex @@ -20,4 +20,9 @@ defmodule Pleroma.Captcha.Service do `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(token :: String.t()) :: :ok end diff --git a/lib/pleroma/captcha/kocaptcha.ex b/lib/pleroma/captcha/kocaptcha.ex index 4ecd1a81f..9891d4031 100644 --- a/lib/pleroma/captcha/kocaptcha.ex +++ b/lib/pleroma/captcha/kocaptcha.ex @@ -29,11 +29,20 @@ defmodule Pleroma.Captcha.Kocaptcha do [{^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) + cleanup(token) true else _ -> false end end + + @impl Service + def cleanup(token) do + # Only delete the entry if it exists in the table, because ets:delete raises an exception if it does not + case :ets.lookup(@ets, token) do + [{^token, _}] -> :ets.delete(@ets, token) + _ -> true + end + end end From 1d31fd0722c27803adc077e2efb424229f31d361 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sun, 16 Dec 2018 22:37:16 +0300 Subject: [PATCH 12/18] Make the hosted kocaptcha the default value --- config/config.exs | 2 +- config/config.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/config/config.exs b/config/config.exs index 21cdc0537..eba401d3e 100644 --- a/config/config.exs +++ b/config/config.exs @@ -15,7 +15,7 @@ config :pleroma, Pleroma.Captcha, minutes_retained: 5, method: Pleroma.Captcha.Kocaptcha -config :pleroma, Pleroma.Captcha.Kocaptcha, endpoint: "http://localhost:9093" +config :pleroma, Pleroma.Captcha.Kocaptcha, endpoint: "https://captcha.kotobank.ch" # Upload configuration config :pleroma, Pleroma.Upload, diff --git a/config/config.md b/config/config.md index 65f0866fd..324f837ce 100644 --- a/config/config.md +++ b/config/config.md @@ -171,6 +171,7 @@ Web Push Notifications configuration. You can use the mix task `mix web_push.gen ### Pleroma.Captcha.Kocaptcha Kocaptcha is a very simple captcha service with a single API endpoint, -the source code is here: https://github.com/koto-bank/kocaptcha +the source code is here: https://github.com/koto-bank/kocaptcha. The default endpoint +`https://captcha.kotobank.ch` is hosted by the developer. * `endpoint`: the kocaptcha endpoint to use \ No newline at end of file From 3a31fdaf0668b4fb4d05c3087674c2b32b6afbe9 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sun, 16 Dec 2018 22:40:44 +0300 Subject: [PATCH 13/18] Change minutes_retained config to seconds_retained --- config/config.exs | 2 +- lib/pleroma/captcha/captcha.ex | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/config.exs b/config/config.exs index eba401d3e..45a22f5da 100644 --- a/config/config.exs +++ b/config/config.exs @@ -12,7 +12,7 @@ config :pleroma, Pleroma.Repo, types: Pleroma.PostgresTypes config :pleroma, Pleroma.Captcha, enabled: false, - minutes_retained: 5, + seconds_retained: 180, method: Pleroma.Captcha.Kocaptcha config :pleroma, Pleroma.Captcha.Kocaptcha, endpoint: "https://captcha.kotobank.ch" diff --git a/lib/pleroma/captcha/captcha.ex b/lib/pleroma/captcha/captcha.ex index 2dcbc4717..26477a214 100644 --- a/lib/pleroma/captcha/captcha.ex +++ b/lib/pleroma/captcha/captcha.ex @@ -40,9 +40,9 @@ defmodule Pleroma.Captcha do else new_captcha = method().new() - minutes_retained = Pleroma.Config.get!([__MODULE__, :minutes_retained]) + seconds_retained = Pleroma.Config.get!([__MODULE__, :seconds_retained]) # Wait several minutes and if the captcha is still there, delete it - Process.send_after(self(), {:cleanup, new_captcha.token}, 1000 * 60 * minutes_retained) + Process.send_after(self(), {:cleanup, new_captcha.token}, 1000 * seconds_retained) {:reply, new_captcha, state} end From 73576ab64ef00aa012dbabae907e03faa436c212 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sun, 16 Dec 2018 23:01:44 +0300 Subject: [PATCH 14/18] Fix captcha tests --- test/captcha_test.exs | 4 ++-- test/support/captcha_mock.ex | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/test/captcha_test.exs b/test/captcha_test.exs index 98e8da79b..2729e1382 100644 --- a/test/captcha_test.exs +++ b/test/captcha_test.exs @@ -11,7 +11,7 @@ defmodule Pleroma.CaptchaTest do ^ets_name = :ets.new(ets_name, @ets_options) mock(fn - %{method: :get, url: "http://localhost:9093/new"} -> + %{method: :get, url: "https://captcha.kotobank.ch/new"} -> json(%{ md5: "63615261b77f5354fb8c4e4986477555", token: "afa1815e14e29355e6c8f6b143a39fa2", @@ -26,7 +26,7 @@ defmodule Pleroma.CaptchaTest do assert Pleroma.Captcha.Kocaptcha.new() == %{ type: :kocaptcha, token: "afa1815e14e29355e6c8f6b143a39fa2", - url: "http://localhost:9093/captchas/afa1815e14e29355e6c8f6b143a39fa2.png" + url: "https://captcha.kotobank.ch/captchas/afa1815e14e29355e6c8f6b143a39fa2.png" } assert Pleroma.Captcha.Kocaptcha.validate( diff --git a/test/support/captcha_mock.ex b/test/support/captcha_mock.ex index 9d79f2e95..560d6c457 100644 --- a/test/support/captcha_mock.ex +++ b/test/support/captcha_mock.ex @@ -7,4 +7,7 @@ defmodule Pleroma.Captcha.Mock do @impl Service def validate(_token, _captcha), do: true + + @impl Service + def cleanup(_token), do: true end From ef6829382aa32c03cf8536422537a9c219bd0035 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sun, 16 Dec 2018 23:41:11 +0300 Subject: [PATCH 15/18] Clean captchas up periodically, not schedule it after theyre created --- lib/pleroma/captcha/captcha.ex | 20 +++++++++++--------- lib/pleroma/captcha/captcha_service.ex | 2 +- lib/pleroma/captcha/kocaptcha.ex | 26 +++++++++++++++++--------- test/support/captcha_mock.ex | 2 +- 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/lib/pleroma/captcha/captcha.ex b/lib/pleroma/captcha/captcha.ex index 26477a214..5630f6b57 100644 --- a/lib/pleroma/captcha/captcha.ex +++ b/lib/pleroma/captcha/captcha.ex @@ -14,6 +14,10 @@ defmodule Pleroma.Captcha do 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 @@ -38,13 +42,7 @@ defmodule Pleroma.Captcha do if !enabled do {:reply, %{type: :none}, state} else - new_captcha = method().new() - - seconds_retained = Pleroma.Config.get!([__MODULE__, :seconds_retained]) - # Wait several minutes and if the captcha is still there, delete it - Process.send_after(self(), {:cleanup, new_captcha.token}, 1000 * seconds_retained) - - {:reply, new_captcha, state} + {:reply, method().new(), state} end end @@ -54,8 +52,12 @@ defmodule Pleroma.Captcha do end @doc false - def handle_info({:cleanup, token}, state) do - method().cleanup(token) + 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} end diff --git a/lib/pleroma/captcha/captcha_service.ex b/lib/pleroma/captcha/captcha_service.ex index fe5a6bf66..8d0b76f86 100644 --- a/lib/pleroma/captcha/captcha_service.ex +++ b/lib/pleroma/captcha/captcha_service.ex @@ -24,5 +24,5 @@ defmodule Pleroma.Captcha.Service do @doc """ This function is called periodically to clean up old captchas """ - @callback cleanup(token :: String.t()) :: :ok + @callback cleanup() :: :ok end diff --git a/lib/pleroma/captcha/kocaptcha.ex b/lib/pleroma/captcha/kocaptcha.ex index 9891d4031..7f9637ad0 100644 --- a/lib/pleroma/captcha/kocaptcha.ex +++ b/lib/pleroma/captcha/kocaptcha.ex @@ -1,4 +1,6 @@ defmodule Pleroma.Captcha.Kocaptcha do + alias Calendar.DateTime + alias Pleroma.Captcha.Service @behaviour Service @@ -17,7 +19,7 @@ defmodule Pleroma.Captcha.Kocaptcha do token = json_resp["token"] - true = :ets.insert(@ets, {token, json_resp["md5"]}) + true = :ets.insert(@ets, {token, json_resp["md5"], DateTime.now_utc()}) %{type: :kocaptcha, token: token, url: endpoint <> json_resp["url"]} end @@ -26,10 +28,10 @@ defmodule Pleroma.Captcha.Kocaptcha do @impl Service def validate(token, captcha) do with false <- is_nil(captcha), - [{^token, saved_md5}] <- :ets.lookup(@ets, token), + [{^token, saved_md5, _}] <- :ets.lookup(@ets, token), true <- :crypto.hash(:md5, captcha) |> Base.encode16() == String.upcase(saved_md5) do # Clear the saved value - cleanup(token) + :ets.delete(@ets, token) true else @@ -38,11 +40,17 @@ defmodule Pleroma.Captcha.Kocaptcha do end @impl Service - def cleanup(token) do - # Only delete the entry if it exists in the table, because ets:delete raises an exception if it does not - case :ets.lookup(@ets, token) do - [{^token, _}] -> :ets.delete(@ets, token) - _ -> true - end + def cleanup() do + seconds_retained = Pleroma.Config.get!([Pleroma.Captcha, :seconds_retained]) + + # Go through captchas and remove expired ones + :ets.tab2list(@ets) + |> Enum.each(fn {token, _, time_inserted} -> + # time created + expiration time = time when the captcha should be removed + remove_time = DateTime.add!(time_inserted, seconds_retained) + if DateTime.after?(DateTime.now_utc(), remove_time), do: :ets.delete(@ets, token) + end) + + :ok end end diff --git a/test/support/captcha_mock.ex b/test/support/captcha_mock.ex index 560d6c457..898aa17b8 100644 --- a/test/support/captcha_mock.ex +++ b/test/support/captcha_mock.ex @@ -9,5 +9,5 @@ defmodule Pleroma.Captcha.Mock do def validate(_token, _captcha), do: true @impl Service - def cleanup(_token), do: true + def cleanup(), do: :ok end From 6e2f64a0a65a2c6fe2aacab164ab0a08341be919 Mon Sep 17 00:00:00 2001 From: vaartis Date: Mon, 17 Dec 2018 06:41:41 +0000 Subject: [PATCH 16/18] minutes->seconds_retained in config.md --- config/config.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.md b/config/config.md index 324f837ce..edabd6e0f 100644 --- a/config/config.md +++ b/config/config.md @@ -167,7 +167,7 @@ Web Push Notifications configuration. You can use the mix task `mix web_push.gen ## Pleroma.Captcha * `enabled`: Whether the captcha should be shown on registration * `method`: The method/service to use for captcha -* `minutes_retained`: The time in minutes for which the captcha is valid (stored in the cache) +* `seconds_retained`: The time in seconds for which the captcha is valid (stored in the cache) ### Pleroma.Captcha.Kocaptcha Kocaptcha is a very simple captcha service with a single API endpoint, From 35522fef0958c2843107a6c9cce546e7e0dfcd44 Mon Sep 17 00:00:00 2001 From: vaartis Date: Mon, 17 Dec 2018 17:19:28 +0300 Subject: [PATCH 17/18] Use :ets.match_delete to delete old captchas --- lib/pleroma/captcha/kocaptcha.ex | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/lib/pleroma/captcha/kocaptcha.ex b/lib/pleroma/captcha/kocaptcha.ex index 7f9637ad0..51900d123 100644 --- a/lib/pleroma/captcha/kocaptcha.ex +++ b/lib/pleroma/captcha/kocaptcha.ex @@ -19,7 +19,11 @@ defmodule Pleroma.Captcha.Kocaptcha do token = json_resp["token"] - true = :ets.insert(@ets, {token, json_resp["md5"], DateTime.now_utc()}) + true = + :ets.insert( + @ets, + {token, json_resp["md5"], DateTime.now_utc() |> DateTime.Format.unix()} + ) %{type: :kocaptcha, token: token, url: endpoint <> json_resp["url"]} end @@ -42,14 +46,21 @@ defmodule Pleroma.Captcha.Kocaptcha do @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() - # Go through captchas and remove expired ones - :ets.tab2list(@ets) - |> Enum.each(fn {token, _, time_inserted} -> - # time created + expiration time = time when the captcha should be removed - remove_time = DateTime.add!(time_inserted, seconds_retained) - if DateTime.after?(DateTime.now_utc(), remove_time), do: :ets.delete(@ets, token) - end) + :ets.select_delete( + @ets, + [ + { + {:_, :_, :"$1"}, + [{:<, :"$1", {:const, delete_after}}], + [true] + } + ] + ) :ok end From de981ac5a23673797951096b64a7f5ca49630467 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Mon, 17 Dec 2018 20:53:42 +0300 Subject: [PATCH 18/18] Alias Kocaptcha in the test --- test/captcha_test.exs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/captcha_test.exs b/test/captcha_test.exs index 2729e1382..54ffbd92f 100644 --- a/test/captcha_test.exs +++ b/test/captcha_test.exs @@ -3,11 +3,13 @@ defmodule Pleroma.CaptchaTest do import Tesla.Mock + alias Pleroma.Captcha.Kocaptcha + @ets_options [:ordered_set, :private, :named_table, {:read_concurrency, true}] describe "Kocaptcha" do setup do - ets_name = Pleroma.Captcha.Kocaptcha.Ets + ets_name = Kocaptcha.Ets ^ets_name = :ets.new(ets_name, @ets_options) mock(fn @@ -23,13 +25,13 @@ defmodule Pleroma.CaptchaTest do end test "new and validate" do - assert Pleroma.Captcha.Kocaptcha.new() == %{ + assert Kocaptcha.new() == %{ type: :kocaptcha, token: "afa1815e14e29355e6c8f6b143a39fa2", url: "https://captcha.kotobank.ch/captchas/afa1815e14e29355e6c8f6b143a39fa2.png" } - assert Pleroma.Captcha.Kocaptcha.validate( + assert Kocaptcha.validate( "afa1815e14e29355e6c8f6b143a39fa2", "7oEy8c" )