hCaptcha (#19)

This commit is contained in:
liamwhite 2020-09-12 13:43:16 -04:00 committed by GitHub
parent c806b08f0f
commit 653fd0a4af
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 169 additions and 306 deletions

View file

@ -1,33 +1,20 @@
/**
* Fetch captchas.
*/
import { $$, hideEl } from './utils/dom';
import { fetchJson, handleError } from './utils/requests';
import { delegate, leftClick } from './utils/events';
import { clearEl, makeEl } from './utils/dom';
function insertCaptcha(checkbox) {
// Also hide any associated labels
checkbox.checked = false;
hideEl(checkbox);
hideEl($$(`label[for="${checkbox.id}"]`));
function insertCaptcha(_event, target) {
const { parentNode, dataset: { sitekey } } = target;
fetchJson('POST', '/captchas')
.then(handleError)
.then(r => r.text())
.then(r => {
checkbox.insertAdjacentHTML('afterend', r);
checkbox.parentElement.removeChild(checkbox);
}).catch(() => {
checkbox.insertAdjacentHTML('afterend', '<p class="block block--danger">Failed to fetch challenge from server!</p>');
checkbox.parentElement.removeChild(checkbox);
});
const script = makeEl('script', {src: 'https://hcaptcha.com/1/api.js', async: true, defer: true});
const frame = makeEl('div', {className: 'h-captcha'});
frame.dataset.sitekey = sitekey;
clearEl(parentNode);
parentNode.insertAdjacentElement('beforeend', frame);
parentNode.insertAdjacentElement('beforeend', script);
}
function bindCaptchaLinks() {
document.addEventListener('click', event => {
if (event.target && event.target.closest('.js-captcha')) {
insertCaptcha(event.target.closest('.js-captcha'));
}
});
export function bindCaptchaLinks() {
delegate(document, 'click', {'.js-captcha': leftClick(insertCaptcha)});
}
export { bindCaptchaLinks };

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

View file

@ -27,6 +27,8 @@ config :philomena,
channel_image_file_root: "priv/static/system/images",
channel_banner_file_root: "priv/static/system/images",
tag_file_root: "priv/static/system/images",
hcaptcha_site_key: "10000000-ffff-ffff-ffff-000000000001",
hcaptcha_secret_key: "0x0000000000000000000000000000000000000000",
cdn_host: "",
proxy_host: nil,
app_dir: File.cwd!()

View file

@ -17,6 +17,8 @@ config :philomena,
channel_image_file_root: System.fetch_env!("CHANNEL_IMAGE_FILE_ROOT"),
channel_banner_file_root: System.fetch_env!("CHANNEL_BANNER_FILE_ROOT"),
anonymous_name_salt: System.fetch_env!("ANONYMOUS_NAME_SALT"),
hcaptcha_secret_key: System.fetch_env!("HCAPTCHA_SECRET_KEY"),
hcaptcha_site_key: System.fetch_env!("HCAPTCHA_SITE_KEY"),
elasticsearch_url: System.get_env("ELASTICSEARCH_URL") || "http://localhost:9200",
advert_file_root: System.fetch_env!("ADVERT_FILE_ROOT"),
avatar_file_root: System.fetch_env!("AVATAR_FILE_ROOT"),

View file

@ -1,154 +0,0 @@
defmodule Philomena.Captcha do
defstruct [:image_base64, :solution, :solution_id]
@numbers ~W(1 2 3 4 5 6)
@images ~W(1 2 3 4 5 6)
@base_path File.cwd!() <> "/assets/static/images/captcha"
@number_files %{
"1" => @base_path <> "/1.png",
"2" => @base_path <> "/2.png",
"3" => @base_path <> "/3.png",
"4" => @base_path <> "/4.png",
"5" => @base_path <> "/5.png",
"6" => @base_path <> "/6.png"
}
@image_files %{
"1" => @base_path <> "/i1.png",
"2" => @base_path <> "/i2.png",
"3" => @base_path <> "/i3.png",
"4" => @base_path <> "/i4.png",
"5" => @base_path <> "/i5.png",
"6" => @base_path <> "/i6.png"
}
@background_file @base_path <> "/background.png"
@geometry %{
1 => "+0+0",
2 => "+120+0",
3 => "+240+0",
4 => "+0+120",
5 => "+120+120",
6 => "+240+120",
7 => "+0+240",
8 => "+120+240",
9 => "+240+240"
}
@distortion_1 [
~W"-implode .1",
~W"-implode -.1"
]
@distortion_2 [
~W"-swirl 10",
~W"-swirl -10",
~W"-swirl 20",
~W"-swirl -20"
]
@distortion_3 [
~W"-wave 5x180",
~W"-wave 5x126",
~W"-wave 10x180",
~W"-wave 10x126"
]
def create do
solution =
Enum.zip(@numbers, Enum.shuffle(@images))
|> Map.new()
# 3x3 render grid
grid = Enum.shuffle(@numbers ++ [nil, nil, nil])
# Base arguments
args = [
"-page",
"360x360",
@background_file
]
# Individual grid files
files =
grid
|> Enum.with_index()
|> Enum.flat_map(fn {num, index} ->
if num do
[
"(",
@image_files[solution[num]],
")",
"-geometry",
@geometry[index + 1],
"-composite",
"(",
@number_files[num],
")",
"-geometry",
@geometry[index + 1],
"-composite"
]
else
[]
end
end)
# Distortions for more unpredictability
distortions =
[
Enum.random(@distortion_1),
Enum.random(@distortion_2),
Enum.random(@distortion_3)
]
|> Enum.shuffle()
|> List.flatten()
jpeg = ~W"-quality 8 jpeg:-"
{image, 0} = System.cmd("convert", args ++ files ++ distortions ++ jpeg)
image = image |> Base.encode64()
# Store solution in redis to prevent reuse
# Solutions are valid for 10 minutes
solution_id =
:crypto.strong_rand_bytes(12)
|> Base.encode16(case: :lower)
solution_id = "cp_" <> solution_id
{:ok, _ok} = Redix.command(:redix, ["SET", solution_id, Jason.encode!(solution)])
{:ok, _ok} = Redix.command(:redix, ["EXPIRE", solution_id, 600])
%Philomena.Captcha{
image_base64: image,
solution: solution,
solution_id: solution_id
}
end
def valid_solution?(<<"cp_", _rest::binary>> = solution_id, solution) when is_map(solution) do
# Delete key immediately. This may race, but should
# have minimal impact if the race succeeds.
with {:ok, sol} <- Redix.command(:redix, ["GET", solution_id]),
{:ok, _del} <- Redix.command(:redix, ["DEL", solution_id]),
{:ok, sol} <- Jason.decode(to_string(sol)) do
Map.equal?(solution, sol)
else
_ ->
false
end
end
def valid_solution?(_solution_id, _solution),
do: false
def valid_solution?(%{"captcha" => %{"id" => id, "sln" => solution}}) do
valid_solution?(id, solution)
end
def valid_solution?(_params),
do: false
end

View file

@ -7,6 +7,10 @@ defmodule Philomena.Http do
Tesla.head(client(headers), url, opts: [adapter: adapter_opts(options)])
end
def post(url, body, headers \\ [], options \\ []) do
Tesla.post(client(headers), url, body, opts: [adapter: adapter_opts(options)])
end
defp adapter_opts(opts) do
opts = Keyword.merge(opts, max_body: 100_000_000)

View file

@ -1,11 +0,0 @@
defmodule PhilomenaWeb.CaptchaController do
use PhilomenaWeb, :controller
alias Philomena.Captcha
def create(conn, _params) do
captcha = Captcha.create()
render(conn, "create.html", captcha: captcha, layout: false)
end
end

View file

@ -3,7 +3,8 @@ defmodule PhilomenaWeb.ConfirmationController do
alias Philomena.Users
plug PhilomenaWeb.CaptchaPlug when action in [:create]
plug PhilomenaWeb.CaptchaPlug
plug PhilomenaWeb.CheckCaptchaPlug when action in [:create]
def new(conn, _params) do
render(conn, "new.html")

View file

@ -9,7 +9,8 @@ defmodule PhilomenaWeb.Conversation.ReportController do
plug PhilomenaWeb.FilterBannedUsersPlug
plug PhilomenaWeb.UserAttributionPlug
plug PhilomenaWeb.CaptchaPlug when action in [:create]
plug PhilomenaWeb.CaptchaPlug
plug PhilomenaWeb.CheckCaptchaPlug when action in [:create]
plug PhilomenaWeb.CanaryMapPlug, new: :show, create: :show
plug :load_and_authorize_resource,

View file

@ -9,7 +9,8 @@ defmodule PhilomenaWeb.Gallery.ReportController do
plug PhilomenaWeb.FilterBannedUsersPlug
plug PhilomenaWeb.UserAttributionPlug
plug PhilomenaWeb.CaptchaPlug when action in [:create]
plug PhilomenaWeb.CaptchaPlug
plug PhilomenaWeb.CheckCaptchaPlug when action in [:create]
plug PhilomenaWeb.CanaryMapPlug, new: :show, create: :show
plug :load_and_authorize_resource,

View file

@ -9,7 +9,8 @@ defmodule PhilomenaWeb.Image.Comment.ReportController do
plug PhilomenaWeb.FilterBannedUsersPlug
plug PhilomenaWeb.UserAttributionPlug
plug PhilomenaWeb.CaptchaPlug when action in [:create]
plug PhilomenaWeb.CaptchaPlug
plug PhilomenaWeb.CheckCaptchaPlug when action in [:create]
plug PhilomenaWeb.CanaryMapPlug, new: :show, create: :show

View file

@ -9,7 +9,8 @@ defmodule PhilomenaWeb.Image.ReportController do
plug PhilomenaWeb.FilterBannedUsersPlug
plug PhilomenaWeb.UserAttributionPlug
plug PhilomenaWeb.CaptchaPlug when action in [:create]
plug PhilomenaWeb.CaptchaPlug
plug PhilomenaWeb.CheckCaptchaPlug when action in [:create]
plug PhilomenaWeb.CanaryMapPlug, new: :show, create: :show
plug :load_and_authorize_resource,

View file

@ -14,6 +14,7 @@ defmodule PhilomenaWeb.Image.SourceController do
plug PhilomenaWeb.FilterBannedUsersPlug
plug PhilomenaWeb.CaptchaPlug
plug PhilomenaWeb.CheckCaptchaPlug
plug PhilomenaWeb.UserAttributionPlug
plug PhilomenaWeb.CanaryMapPlug, update: :edit_metadata
plug :load_and_authorize_resource, model: Image, id_name: "image_id", preload: [:tags, :user]

View file

@ -16,6 +16,7 @@ defmodule PhilomenaWeb.Image.TagController do
plug PhilomenaWeb.FilterBannedUsersPlug
plug PhilomenaWeb.CaptchaPlug
plug PhilomenaWeb.CheckCaptchaPlug
plug PhilomenaWeb.UserAttributionPlug
plug PhilomenaWeb.CanaryMapPlug, update: :edit_metadata
plug :load_and_authorize_resource, model: Image, id_name: "image_id", preload: [:tags, :user]

View file

@ -27,7 +27,8 @@ defmodule PhilomenaWeb.ImageController do
plug PhilomenaWeb.FilterBannedUsersPlug when action in [:new, :create]
plug PhilomenaWeb.UserAttributionPlug when action in [:create]
plug PhilomenaWeb.CaptchaPlug when action in [:create]
plug PhilomenaWeb.CaptchaPlug when action in [:new, :show, :create]
plug PhilomenaWeb.CheckCaptchaPlug when action in [:create]
plug PhilomenaWeb.ScraperPlug,
[params_name: "image", params_key: "image"] when action in [:create]

View file

@ -3,7 +3,8 @@ defmodule PhilomenaWeb.PasswordController do
alias Philomena.Users
plug PhilomenaWeb.CaptchaPlug when action in [:create]
plug PhilomenaWeb.CaptchaPlug when action in [:new, :create]
plug PhilomenaWeb.CheckCaptchaPlug when action in [:create]
plug PhilomenaWeb.CompromisedPasswordCheckPlug when action in [:update]
plug :get_user_by_reset_password_token when action in [:edit, :update]

View file

@ -9,7 +9,8 @@ defmodule PhilomenaWeb.Profile.Commission.ReportController do
plug PhilomenaWeb.FilterBannedUsersPlug
plug PhilomenaWeb.UserAttributionPlug
plug PhilomenaWeb.CaptchaPlug when action in [:create]
plug PhilomenaWeb.CaptchaPlug
plug PhilomenaWeb.CheckCaptchaPlug when action in [:create]
plug PhilomenaWeb.CanaryMapPlug, new: :show, create: :show
plug :load_resource,

View file

@ -9,7 +9,8 @@ defmodule PhilomenaWeb.Profile.ReportController do
plug PhilomenaWeb.FilterBannedUsersPlug
plug PhilomenaWeb.UserAttributionPlug
plug PhilomenaWeb.CaptchaPlug when action in [:create]
plug PhilomenaWeb.CaptchaPlug
plug PhilomenaWeb.CheckCaptchaPlug when action in [:create]
plug PhilomenaWeb.CanaryMapPlug, new: :show, create: :show
plug :load_and_authorize_resource,

View file

@ -4,7 +4,8 @@ defmodule PhilomenaWeb.RegistrationController do
alias Philomena.Users
alias Philomena.Users.User
plug PhilomenaWeb.CaptchaPlug when action in [:create]
plug PhilomenaWeb.CaptchaPlug when action in [:new, :create]
plug PhilomenaWeb.CheckCaptchaPlug when action in [:create]
plug PhilomenaWeb.CompromisedPasswordCheckPlug when action in [:create]
plug :assign_email_and_password_changesets when action in [:edit]

View file

@ -29,7 +29,8 @@ defmodule PhilomenaWeb.ReportController do
#
# plug PhilomenaWeb.FilterBannedUsersPlug
# plug PhilomenaWeb.UserAttributionPlug
# plug PhilomenaWeb.CaptchaPlug when action in [:create]
# plug PhilomenaWeb.CaptchaPlug
# plug PhilomenaWeb.CheckCaptchaPlug when action in [:create]
# plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true
def create(conn, action, reportable, reportable_type, %{"report" => report_params}) do

View file

@ -9,7 +9,8 @@ defmodule PhilomenaWeb.Topic.Post.ReportController do
plug PhilomenaWeb.FilterBannedUsersPlug
plug PhilomenaWeb.UserAttributionPlug
plug PhilomenaWeb.CaptchaPlug when action in [:create]
plug PhilomenaWeb.CaptchaPlug
plug PhilomenaWeb.CheckCaptchaPlug when action in [:create]
plug PhilomenaWeb.CanaryMapPlug, new: :show, create: :show

View file

@ -3,7 +3,8 @@ defmodule PhilomenaWeb.UnlockController do
alias Philomena.Users
plug PhilomenaWeb.CaptchaPlug when action in [:create]
plug PhilomenaWeb.CaptchaPlug
plug PhilomenaWeb.CheckCaptchaPlug when action in [:create]
def new(conn, _params) do
render(conn, "new.html")

View file

@ -1,53 +1,29 @@
defmodule PhilomenaWeb.CaptchaPlug do
alias Philomena.Captcha
import Plug.Conn
import Phoenix.Controller
alias PhilomenaWeb.ContentSecurityPolicyPlug
def init([]), do: false
@hcaptcha_url ["https://hcaptcha.com", "https://*.hcaptcha.com"]
def init(_opts) do
[]
end
# Set CSP headers for serving captchas.
# Only holepunch CSP if the user is not signed in.
@spec call(Plug.Conn.t(), any()) :: Plug.Conn.t()
def call(conn, _opts) do
case captcha_enabled?() do
true -> maybe_check_captcha(conn, conn.assigns.current_user)
false -> conn
end
user = conn.assigns.current_user
maybe_assign_csp_headers(conn, user)
end
defp maybe_check_captcha(conn, nil) do
case Captcha.valid_solution?(conn.params) do
true ->
conn
false ->
conn
|> put_flash(
:error,
"There was an error verifying you're not a robot. Please try again."
)
|> do_failure_response(ajax?(conn))
|> halt()
end
end
defp maybe_check_captcha(conn, _user), do: conn
defp do_failure_response(conn, true) do
defp maybe_assign_csp_headers(conn, nil) do
conn
|> put_status(:multiple_choices)
|> text("")
|> ContentSecurityPolicyPlug.permit_source(:script_src, @hcaptcha_url)
|> ContentSecurityPolicyPlug.permit_source(:frame_src, @hcaptcha_url)
|> ContentSecurityPolicyPlug.permit_source(:style_src, @hcaptcha_url)
end
defp do_failure_response(conn, _false) do
redirect(conn, external: conn.assigns.referrer)
end
def ajax?(conn) do
case get_req_header(conn, "x-requested-with") do
[value] -> String.downcase(value) == "xmlhttprequest"
_ -> false
end
end
def captcha_enabled? do
Application.get_env(:philomena, :captcha) != false
defp maybe_assign_csp_headers(conn, _user) do
conn
end
end

View file

@ -0,0 +1,71 @@
defmodule PhilomenaWeb.CheckCaptchaPlug do
import Plug.Conn
import Phoenix.Controller
def init([]), do: false
def call(conn, _opts) do
case captcha_enabled?() do
true -> maybe_check_captcha(conn, conn.assigns.current_user)
false -> conn
end
end
defp maybe_check_captcha(conn, nil) do
case valid_solution?(conn.params) do
true ->
conn
false ->
conn
|> put_flash(
:error,
"There was an error verifying you're not a robot. Please try again."
)
|> do_failure_response(ajax?(conn))
|> halt()
end
end
defp maybe_check_captcha(conn, _user), do: conn
defp valid_solution?(%{"h-captcha-response" => captcha_token}) do
{:ok, %{body: body, status: 200}} =
Philomena.Http.post(
"https://hcaptcha.com/siteverify",
URI.encode_query(%{"response" => captcha_token, "secret" => hcaptcha_secret_key()}),
[{"Content-Type", "application/x-www-form-urlencoded"}]
)
body
|> Jason.decode!()
|> Map.get("success", false)
end
defp valid_solution?(_params), do: false
defp do_failure_response(conn, true) do
conn
|> put_status(:multiple_choices)
|> text("")
end
defp do_failure_response(conn, _false) do
redirect(conn, external: conn.assigns.referrer)
end
def ajax?(conn) do
case get_req_header(conn, "x-requested-with") do
[value] -> String.downcase(value) == "xmlhttprequest"
_ -> false
end
end
def captcha_enabled? do
Application.get_env(:philomena, :captcha) != false
end
def hcaptcha_secret_key do
Application.get_env(:philomena, :hcaptcha_secret_key)
end
end

View file

@ -47,7 +47,7 @@ defmodule PhilomenaWeb.ContentSecurityPolicyPlug do
def permit_source(conn, key, value) when key in @allowed_sources do
conn
|> get_config()
|> Keyword.update(key, [], &[value | &1])
|> Keyword.update(key, value, &(value ++ &1))
|> set_config(conn)
end

View file

@ -486,8 +486,6 @@ defmodule PhilomenaWeb.Router do
resources "/source_changes", Profile.SourceChangeController, only: [:index]
end
resources "/captchas", CaptchaController, only: [:create]
scope "/posts", Post, as: :post do
resources "/preview", PreviewController, only: [:create]
end

View file

@ -0,0 +1,8 @@
= if is_nil(@conn.assigns.current_user) do
- challenge = challenge_name(@name)
.field.js-captcha-box
br
=> checkbox :captcha, challenge, class: "js-captcha", value: 0, autocomplete: "off", data: [sitekey: hcaptcha_site_key()]
= label :captcha, challenge, "I am not a robot!"
br

View file

@ -1,20 +0,0 @@
elixir:
options = [
"Applejack": 1,
"Fluttershy": 2,
"Pinkie Pie": 3,
"Rainbow Dash": 4,
"Rarity": 5,
"Twilight Sparkle": 6
]
div
= hidden_input :captcha, :id, value: @captcha.solution_id
img src="data:image/jpeg;base64,#{@captcha.image_base64}"
= for i <- (1..6) do
.field
label> for="captcha_sln[#{i}]"
| Name of pony with cutie mark #
= i
= select :captcha, "sln[#{i}]", options, class: "input", name: "captcha[sln][#{i}]"

View file

@ -4,9 +4,7 @@ h1 Resend confirmation instructions
.field
= email_input f, :email, placeholder: "Email", class: "input", required: true
.field
= checkbox f, :captcha, class: "js-captcha", value: 0
= label f, :captcha, "I am not a robot!"
= render PhilomenaWeb.CaptchaView, "_captcha.html", name: "confirmation", conn: @conn
div
= submit "Resend confirmation instructions", class: "button"

View file

@ -12,9 +12,7 @@
button.button.button--separate-left type="reset" data-click-hide="#source-form" data-click-show="#image-source"
' Cancel
= if !@conn.assigns.current_user do
= checkbox f, :captcha, class: "js-captcha", value: 0
= label f, :captcha, "I am not a robot!"
= render PhilomenaWeb.CaptchaView, "_captcha.html", name: "source", conn: @conn
- else
p

View file

@ -33,10 +33,8 @@
a> href="/pages/tags" tagging guidelines
' before editing tags.
.field
= checkbox f, :captcha, class: "js-captcha", value: 0
= label f, :captcha, "I am not a robot!"
= render PhilomenaWeb.CaptchaView, "_captcha.html", name: "tags", conn: @conn
ul.horizontal-list
li
.actions

View file

@ -9,15 +9,6 @@
' Don't post content the artist doesn't want here (or shared in general),
strong including commercial content.
= if !@conn.assigns.current_user do
p
strong<> Sorry, but due to spam, anonymous uploaders need to fill this out.
| If you're logged in, you can still post anonymously and won't have to deal with captchas!
.field
= checkbox f, :captcha, class: "js-captcha", value: 0
= label f, :captcha, "I am not a robot!"
p
strong
' Please check it isn't already here with
@ -90,5 +81,7 @@
= label f, :anonymous, "Post anonymously"
= checkbox f, :anonymous, class: "checkbox", value: anonymous_by_default?(@conn)
= render PhilomenaWeb.CaptchaView, "_captcha.html", name: "image", conn: @conn
.actions
= submit "Upload", class: "button", autocomplete: "off", data: [disable_with: "Please wait..."]

View file

@ -7,8 +7,6 @@ p
.field
= email_input f, :email, class: "input", placeholder: "Email", required: true
.field
= checkbox f, :captcha, class: "js-captcha", value: 0
= label f, :captcha, "I am not a robot!"
= render PhilomenaWeb.CaptchaView, "_captcha.html", name: "password", conn: @conn
= submit "Send instructions to reset password", class: "button"

View file

@ -28,9 +28,7 @@ h1 Register
= password_input f, :password_confirmation, class: "input", placeholder: "Confirm password", required: true
= error_tag f, :password_confirmation
.field
= checkbox f, :captcha, class: "js-captcha", value: 0
= label f, :captcha, "I am not a robot!"
= render PhilomenaWeb.CaptchaView, "_captcha.html", name: "registration", conn: @conn
br

View file

@ -60,11 +60,6 @@ p
.block__tab.communication-edit__tab.hidden data-tab="preview"
' [Loading preview...]
= if !@conn.assigns.current_user do
.field
= checkbox f, :captcha, class: "js-captcha", value: 0
= label f, :captcha, "I am not a robot!"
p
' This helps stop bot spam; log in if you don't want to deal with captchas.
= render PhilomenaWeb.CaptchaView, "_captcha.html", name: "report", conn: @conn
= submit "Send Report", class: "button"
= submit "Send Report", class: "button"

View file

@ -4,9 +4,7 @@ h1 Resend unlock instructions
.field
= email_input f, :email, placeholder: "Email", class: "input", required: true
.field
= checkbox f, :captcha, class: "js-captcha", value: 0
= label f, :captcha, "I am not a robot!"
= render PhilomenaWeb.CaptchaView, "_captcha.html", name: "unlock", conn: @conn
div
= submit "Resend unlock instructions", class: "button"

View file

@ -1,3 +1,12 @@
defmodule PhilomenaWeb.CaptchaView do
use PhilomenaWeb, :view
# Prevent ID collisions if multiple forms are on the page.
def challenge_name(name) do
"#{name}_challenge"
end
def hcaptcha_site_key do
Application.get_env(:philomena, :hcaptcha_site_key)
end
end