Add base CAPTCHA support (currently only kocaptcha)
This commit is contained in:
parent
e74f384b68
commit
a2399c1c7c
|
@ -10,6 +10,13 @@
|
||||||
|
|
||||||
config :pleroma, Pleroma.Repo, types: Pleroma.PostgresTypes
|
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
|
# Upload configuration
|
||||||
config :pleroma, Pleroma.Upload,
|
config :pleroma, Pleroma.Upload,
|
||||||
uploader: Pleroma.Uploaders.Local,
|
uploader: Pleroma.Uploaders.Local,
|
||||||
|
|
|
@ -24,6 +24,7 @@ def start(_type, _args) do
|
||||||
# Start the Ecto repository
|
# Start the Ecto repository
|
||||||
supervisor(Pleroma.Repo, []),
|
supervisor(Pleroma.Repo, []),
|
||||||
worker(Pleroma.Emoji, []),
|
worker(Pleroma.Emoji, []),
|
||||||
|
worker(Pleroma.Captcha, []),
|
||||||
worker(
|
worker(
|
||||||
Cachex,
|
Cachex,
|
||||||
[
|
[
|
||||||
|
|
|
@ -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
|
|
@ -99,6 +99,7 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/password_reset/:token", UtilController, :show_password_reset)
|
get("/password_reset/:token", UtilController, :show_password_reset)
|
||||||
post("/password_reset", UtilController, :password_reset)
|
post("/password_reset", UtilController, :password_reset)
|
||||||
get("/emoji", UtilController, :emoji)
|
get("/emoji", UtilController, :emoji)
|
||||||
|
get("/captcha", UtilController, :captcha)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do
|
scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do
|
||||||
|
|
|
@ -284,4 +284,8 @@ def delete_account(%{assigns: %{user: user}} = conn, params) do
|
||||||
json(conn, %{error: msg})
|
json(conn, %{error: msg})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def captcha(conn, _params) do
|
||||||
|
json(conn, Pleroma.Captcha.new())
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -132,38 +132,47 @@ def register_user(params) do
|
||||||
bio: User.parse_bio(params["bio"]),
|
bio: User.parse_bio(params["bio"]),
|
||||||
email: params["email"],
|
email: params["email"],
|
||||||
password: params["password"],
|
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
|
# no need to query DB if registration is open
|
||||||
token =
|
token =
|
||||||
unless registrations_open || is_nil(tokenString) do
|
unless registrations_open || is_nil(tokenString) do
|
||||||
Repo.get_by(UserInviteToken, %{token: tokenString})
|
Repo.get_by(UserInviteToken, %{token: tokenString})
|
||||||
end
|
end
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
registrations_open || (!is_nil(token) && !token.used) ->
|
registrations_open || (!is_nil(token) && !token.used) ->
|
||||||
changeset = User.register_changeset(%User{info: %{}}, params)
|
changeset = User.register_changeset(%User{info: %{}}, params)
|
||||||
|
|
||||||
with {:ok, user} <- Repo.insert(changeset) do
|
with {:ok, user} <- Repo.insert(changeset) do
|
||||||
!registrations_open && UserInviteToken.mark_as_used(token.token)
|
!registrations_open && UserInviteToken.mark_as_used(token.token)
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
else
|
else
|
||||||
{:error, changeset} ->
|
{:error, changeset} ->
|
||||||
errors =
|
errors =
|
||||||
Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end)
|
Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end)
|
||||||
|> Jason.encode!()
|
|> Jason.encode!()
|
||||||
|
|
||||||
{:error, %{error: errors}}
|
{:error, %{error: errors}}
|
||||||
end
|
end
|
||||||
|
|
||||||
!registrations_open && is_nil(token) ->
|
|
||||||
{:error, "Invalid token"}
|
|
||||||
|
|
||||||
!registrations_open && token.used ->
|
!registrations_open && is_nil(token) ->
|
||||||
{:error, "Expired token"}
|
{:error, "Invalid token"}
|
||||||
|
|
||||||
|
!registrations_open && token.used ->
|
||||||
|
{:error, "Expired token"}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue