Add HCaptcha support (#304)

* Add HCaptcha support

* Actually verify password reset requests

* Fix password request when captcha not configured

* Add configuration for prod release
This commit is contained in:
Uku Taht 2020-08-28 15:00:16 +03:00 committed by GitHub
parent 4aa4dfdcaf
commit 9feda6a3d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 109 additions and 34 deletions

View File

@ -184,6 +184,10 @@ config :plausible, :custom_domain_server,
config :plausible, PlausibleWeb.Firewall,
blocklist: System.get_env("IP_BLOCKLIST", "") |> String.split(",") |> Enum.map(&String.trim/1)
config :plausible, :hcaptcha,
sitekey: System.get_env("HCAPTCHA_SITEKEY"),
secret: System.get_env("HCAPTCHA_SECRET")
config :geolix,
databases: [
%{

View File

@ -53,6 +53,9 @@ custom_domain_server_user = System.get_env("CUSTOM_DOMAIN_SERVER_USER")
custom_domain_server_password = System.get_env("CUSTOM_DOMAIN_SERVER_PASSWORD")
geolite2_country_db = System.get_env("GEOLITE2_COUNTRY_DB")
disable_auth = String.to_existing_atom(System.get_env("DISABLE_AUTH", "false"))
hcaptcha_sitekey = System.get_env("HCAPTCHA_SITEKEY")
hcaptcha_secret = System.get_env("HCAPTCHA_SECRET")
config :plausible,
admin_user: admin_user,
@ -187,6 +190,10 @@ config :plausible, Oban,
queues: if(cron_enabled, do: base_queues ++ extra_queues, else: base_queues),
crontab: if(cron_enabled, do: base_cron ++ extra_cron, else: base_cron)
config :plausible, :hcaptcha,
sitekey: hcaptcha_sitekey,
secret: hcaptcha_secret
config :ref_inspector,
init: {Plausible.Release, :configure_ref_inspector}

View File

@ -54,4 +54,5 @@ config :geolix,
]
config :plausible,
session_timeout: 0
session_timeout: 0,
environment: System.get_env("ENVIRONMENT", "test")

View File

@ -0,0 +1,27 @@
defmodule PlausibleWeb.Captcha do
@verify_endpoint "https://hcaptcha.com/siteverify"
def enabled? do
!!sitekey()
end
def sitekey() do
Application.get_env(:plausible, :hcaptcha, [])
|> Keyword.fetch!(:sitekey)
end
def verify(token) do
if enabled?() do
res = HTTPoison.post!(@verify_endpoint, {:form, [{"response", token}, {"secret", secret()}]})
json = Jason.decode!(res.body)
json["success"]
else
true
end
end
defp secret() do
Application.get_env(:plausible, :hcaptcha, [])
|> Keyword.fetch!(:secret)
end
end

View File

@ -24,28 +24,36 @@ defmodule PlausibleWeb.AuthController do
end
end
def register(conn, %{"user" => params}) do
user = Plausible.Auth.User.changeset(%Plausible.Auth.User{}, params)
def register(conn, params) do
user = Plausible.Auth.User.changeset(%Plausible.Auth.User{}, params["user"])
case Ecto.Changeset.apply_action(user, :insert) do
{:ok, user} ->
token = Auth.Token.sign_activation(user.name, user.email)
url = PlausibleWeb.Endpoint.clean_url() <> "/claim-activation?token=#{token}"
Logger.info(url)
email_template = PlausibleWeb.Email.activation_email(user, url)
Plausible.Mailer.send_email(email_template)
if PlausibleWeb.Captcha.verify(params["h-captcha-response"]) do
case Ecto.Changeset.apply_action(user, :insert) do
{:ok, user} ->
token = Auth.Token.sign_activation(user.name, user.email)
url = PlausibleWeb.Endpoint.clean_url() <> "/claim-activation?token=#{token}"
Logger.info(url)
email_template = PlausibleWeb.Email.activation_email(user, url)
Plausible.Mailer.send_email(email_template)
conn
|> render("register_success.html",
email: user.email,
layout: {PlausibleWeb.LayoutView, "focus.html"}
)
conn
|> render("register_success.html",
email: user.email,
layout: {PlausibleWeb.LayoutView, "focus.html"}
)
{:error, changeset} ->
render(conn, "register_form.html",
changeset: changeset,
layout: {PlausibleWeb.LayoutView, "focus.html"}
)
{:error, changeset} ->
render(conn, "register_form.html",
changeset: changeset,
layout: {PlausibleWeb.LayoutView, "focus.html"}
)
end
else
render(conn, "register_form.html",
changeset: user,
captcha_error: "Please complete the captcha to register",
layout: {PlausibleWeb.LayoutView, "focus.html"}
)
end
end
@ -87,23 +95,30 @@ defmodule PlausibleWeb.AuthController do
)
end
def password_reset_request(conn, %{"email" => email}) do
user = Repo.get_by(Plausible.Auth.User, email: email)
def password_reset_request(conn, %{"email" => email} = params) do
if PlausibleWeb.Captcha.verify(params["h-captcha-response"]) do
user = Repo.get_by(Plausible.Auth.User, email: email)
if user do
token = Auth.Token.sign_password_reset(email)
url = PlausibleWeb.Endpoint.clean_url() <> "/password/reset?token=#{token}"
Logger.debug("PASSWORD RESET LINK: " <> url)
email_template = PlausibleWeb.Email.password_reset_email(email, url)
Plausible.Mailer.deliver_now(email_template)
if user do
token = Auth.Token.sign_password_reset(email)
url = PlausibleWeb.Endpoint.clean_url() <> "/password/reset?token=#{token}"
Logger.debug("PASSWORD RESET LINK: " <> url)
email_template = PlausibleWeb.Email.password_reset_email(email, url)
Plausible.Mailer.deliver_now(email_template)
render(conn, "password_reset_request_success.html",
email: email,
layout: {PlausibleWeb.LayoutView, "focus.html"}
)
render(conn, "password_reset_request_success.html",
email: email,
layout: {PlausibleWeb.LayoutView, "focus.html"}
)
else
render(conn, "password_reset_request_success.html",
email: email,
layout: {PlausibleWeb.LayoutView, "focus.html"}
)
end
else
render(conn, "password_reset_request_success.html",
email: email,
render(conn, "password_reset_request_form.html",
error: "Please complete the captcha to reset your password",
layout: {PlausibleWeb.LayoutView, "focus.html"}
)
end

View File

@ -7,5 +7,16 @@
<%= if @conn.assigns[:error] do %>
<div class="text-red-500 text-xs italic my-2"><%= @conn.assigns[:error] %></div>
<% end %>
<%= if PlausibleWeb.Captcha.enabled?() do %>
<div class="mt-4">
<div class="h-captcha" data-sitekey="<%= PlausibleWeb.Captcha.sitekey() %>"></div>
<%= if assigns[:captcha_error] do %>
<div class="text-red-500 text-xs italic mt-3"><%= @captcha_error %></div>
<% end %>
<script src="https://hcaptcha.com/1/api.js" async defer></script>
</div>
<% end %>
<%= submit "Send reset link →", class: "button mt-4 w-full" %>
<% end %>

View File

@ -19,6 +19,16 @@
<%= error_tag f, :email %>
</div>
<%= if PlausibleWeb.Captcha.enabled?() do %>
<div class="mt-4">
<div class="h-captcha" data-sitekey="<%= PlausibleWeb.Captcha.sitekey() %>"></div>
<%= if assigns[:captcha_error] do %>
<div class="text-red-500 text-xs italic mt-3"><%= @captcha_error %></div>
<% end %>
<script src="https://hcaptcha.com/1/api.js" async defer></script>
</div>
<% end %>
<%= submit "Start my free trial →", class: "button mt-4 w-full" %>
<p class="text-center text-gray-600 text-xs mt-4">