hCaptcha (#19)
|
@ -1,33 +1,20 @@
|
||||||
/**
|
import { delegate, leftClick } from './utils/events';
|
||||||
* Fetch captchas.
|
import { clearEl, makeEl } from './utils/dom';
|
||||||
*/
|
|
||||||
import { $$, hideEl } from './utils/dom';
|
|
||||||
import { fetchJson, handleError } from './utils/requests';
|
|
||||||
|
|
||||||
function insertCaptcha(checkbox) {
|
function insertCaptcha(_event, target) {
|
||||||
// Also hide any associated labels
|
const { parentNode, dataset: { sitekey } } = target;
|
||||||
checkbox.checked = false;
|
|
||||||
hideEl(checkbox);
|
|
||||||
hideEl($$(`label[for="${checkbox.id}"]`));
|
|
||||||
|
|
||||||
fetchJson('POST', '/captchas')
|
const script = makeEl('script', {src: 'https://hcaptcha.com/1/api.js', async: true, defer: true});
|
||||||
.then(handleError)
|
const frame = makeEl('div', {className: 'h-captcha'});
|
||||||
.then(r => r.text())
|
|
||||||
.then(r => {
|
frame.dataset.sitekey = sitekey;
|
||||||
checkbox.insertAdjacentHTML('afterend', r);
|
|
||||||
checkbox.parentElement.removeChild(checkbox);
|
clearEl(parentNode);
|
||||||
}).catch(() => {
|
|
||||||
checkbox.insertAdjacentHTML('afterend', '<p class="block block--danger">Failed to fetch challenge from server!</p>');
|
parentNode.insertAdjacentElement('beforeend', frame);
|
||||||
checkbox.parentElement.removeChild(checkbox);
|
parentNode.insertAdjacentElement('beforeend', script);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindCaptchaLinks() {
|
export function bindCaptchaLinks() {
|
||||||
document.addEventListener('click', event => {
|
delegate(document, 'click', {'.js-captcha': leftClick(insertCaptcha)});
|
||||||
if (event.target && event.target.closest('.js-captcha')) {
|
|
||||||
insertCaptcha(event.target.closest('.js-captcha'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { bindCaptchaLinks };
|
|
||||||
|
|
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 5.4 KiB |
|
@ -27,6 +27,8 @@ config :philomena,
|
||||||
channel_image_file_root: "priv/static/system/images",
|
channel_image_file_root: "priv/static/system/images",
|
||||||
channel_banner_file_root: "priv/static/system/images",
|
channel_banner_file_root: "priv/static/system/images",
|
||||||
tag_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: "",
|
cdn_host: "",
|
||||||
proxy_host: nil,
|
proxy_host: nil,
|
||||||
app_dir: File.cwd!()
|
app_dir: File.cwd!()
|
||||||
|
|
|
@ -17,6 +17,8 @@ config :philomena,
|
||||||
channel_image_file_root: System.fetch_env!("CHANNEL_IMAGE_FILE_ROOT"),
|
channel_image_file_root: System.fetch_env!("CHANNEL_IMAGE_FILE_ROOT"),
|
||||||
channel_banner_file_root: System.fetch_env!("CHANNEL_BANNER_FILE_ROOT"),
|
channel_banner_file_root: System.fetch_env!("CHANNEL_BANNER_FILE_ROOT"),
|
||||||
anonymous_name_salt: System.fetch_env!("ANONYMOUS_NAME_SALT"),
|
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",
|
elasticsearch_url: System.get_env("ELASTICSEARCH_URL") || "http://localhost:9200",
|
||||||
advert_file_root: System.fetch_env!("ADVERT_FILE_ROOT"),
|
advert_file_root: System.fetch_env!("ADVERT_FILE_ROOT"),
|
||||||
avatar_file_root: System.fetch_env!("AVATAR_FILE_ROOT"),
|
avatar_file_root: System.fetch_env!("AVATAR_FILE_ROOT"),
|
||||||
|
|
|
@ -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
|
|
|
@ -7,6 +7,10 @@ defmodule Philomena.Http do
|
||||||
Tesla.head(client(headers), url, opts: [adapter: adapter_opts(options)])
|
Tesla.head(client(headers), url, opts: [adapter: adapter_opts(options)])
|
||||||
end
|
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
|
defp adapter_opts(opts) do
|
||||||
opts = Keyword.merge(opts, max_body: 100_000_000)
|
opts = Keyword.merge(opts, max_body: 100_000_000)
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
|
@ -3,7 +3,8 @@ defmodule PhilomenaWeb.ConfirmationController do
|
||||||
|
|
||||||
alias Philomena.Users
|
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
|
def new(conn, _params) do
|
||||||
render(conn, "new.html")
|
render(conn, "new.html")
|
||||||
|
|
|
@ -9,7 +9,8 @@ defmodule PhilomenaWeb.Conversation.ReportController do
|
||||||
|
|
||||||
plug PhilomenaWeb.FilterBannedUsersPlug
|
plug PhilomenaWeb.FilterBannedUsersPlug
|
||||||
plug PhilomenaWeb.UserAttributionPlug
|
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 PhilomenaWeb.CanaryMapPlug, new: :show, create: :show
|
||||||
|
|
||||||
plug :load_and_authorize_resource,
|
plug :load_and_authorize_resource,
|
||||||
|
|
|
@ -9,7 +9,8 @@ defmodule PhilomenaWeb.Gallery.ReportController do
|
||||||
|
|
||||||
plug PhilomenaWeb.FilterBannedUsersPlug
|
plug PhilomenaWeb.FilterBannedUsersPlug
|
||||||
plug PhilomenaWeb.UserAttributionPlug
|
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 PhilomenaWeb.CanaryMapPlug, new: :show, create: :show
|
||||||
|
|
||||||
plug :load_and_authorize_resource,
|
plug :load_and_authorize_resource,
|
||||||
|
|
|
@ -9,7 +9,8 @@ defmodule PhilomenaWeb.Image.Comment.ReportController do
|
||||||
|
|
||||||
plug PhilomenaWeb.FilterBannedUsersPlug
|
plug PhilomenaWeb.FilterBannedUsersPlug
|
||||||
plug PhilomenaWeb.UserAttributionPlug
|
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 PhilomenaWeb.CanaryMapPlug, new: :show, create: :show
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,8 @@ defmodule PhilomenaWeb.Image.ReportController do
|
||||||
|
|
||||||
plug PhilomenaWeb.FilterBannedUsersPlug
|
plug PhilomenaWeb.FilterBannedUsersPlug
|
||||||
plug PhilomenaWeb.UserAttributionPlug
|
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 PhilomenaWeb.CanaryMapPlug, new: :show, create: :show
|
||||||
|
|
||||||
plug :load_and_authorize_resource,
|
plug :load_and_authorize_resource,
|
||||||
|
|
|
@ -14,6 +14,7 @@ defmodule PhilomenaWeb.Image.SourceController do
|
||||||
|
|
||||||
plug PhilomenaWeb.FilterBannedUsersPlug
|
plug PhilomenaWeb.FilterBannedUsersPlug
|
||||||
plug PhilomenaWeb.CaptchaPlug
|
plug PhilomenaWeb.CaptchaPlug
|
||||||
|
plug PhilomenaWeb.CheckCaptchaPlug
|
||||||
plug PhilomenaWeb.UserAttributionPlug
|
plug PhilomenaWeb.UserAttributionPlug
|
||||||
plug PhilomenaWeb.CanaryMapPlug, update: :edit_metadata
|
plug PhilomenaWeb.CanaryMapPlug, update: :edit_metadata
|
||||||
plug :load_and_authorize_resource, model: Image, id_name: "image_id", preload: [:tags, :user]
|
plug :load_and_authorize_resource, model: Image, id_name: "image_id", preload: [:tags, :user]
|
||||||
|
|
|
@ -16,6 +16,7 @@ defmodule PhilomenaWeb.Image.TagController do
|
||||||
|
|
||||||
plug PhilomenaWeb.FilterBannedUsersPlug
|
plug PhilomenaWeb.FilterBannedUsersPlug
|
||||||
plug PhilomenaWeb.CaptchaPlug
|
plug PhilomenaWeb.CaptchaPlug
|
||||||
|
plug PhilomenaWeb.CheckCaptchaPlug
|
||||||
plug PhilomenaWeb.UserAttributionPlug
|
plug PhilomenaWeb.UserAttributionPlug
|
||||||
plug PhilomenaWeb.CanaryMapPlug, update: :edit_metadata
|
plug PhilomenaWeb.CanaryMapPlug, update: :edit_metadata
|
||||||
plug :load_and_authorize_resource, model: Image, id_name: "image_id", preload: [:tags, :user]
|
plug :load_and_authorize_resource, model: Image, id_name: "image_id", preload: [:tags, :user]
|
||||||
|
|
|
@ -27,7 +27,8 @@ defmodule PhilomenaWeb.ImageController do
|
||||||
|
|
||||||
plug PhilomenaWeb.FilterBannedUsersPlug when action in [:new, :create]
|
plug PhilomenaWeb.FilterBannedUsersPlug when action in [:new, :create]
|
||||||
plug PhilomenaWeb.UserAttributionPlug when action in [: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,
|
plug PhilomenaWeb.ScraperPlug,
|
||||||
[params_name: "image", params_key: "image"] when action in [:create]
|
[params_name: "image", params_key: "image"] when action in [:create]
|
||||||
|
|
|
@ -3,7 +3,8 @@ defmodule PhilomenaWeb.PasswordController do
|
||||||
|
|
||||||
alias Philomena.Users
|
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 PhilomenaWeb.CompromisedPasswordCheckPlug when action in [:update]
|
||||||
plug :get_user_by_reset_password_token when action in [:edit, :update]
|
plug :get_user_by_reset_password_token when action in [:edit, :update]
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,8 @@ defmodule PhilomenaWeb.Profile.Commission.ReportController do
|
||||||
|
|
||||||
plug PhilomenaWeb.FilterBannedUsersPlug
|
plug PhilomenaWeb.FilterBannedUsersPlug
|
||||||
plug PhilomenaWeb.UserAttributionPlug
|
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 PhilomenaWeb.CanaryMapPlug, new: :show, create: :show
|
||||||
|
|
||||||
plug :load_resource,
|
plug :load_resource,
|
||||||
|
|
|
@ -9,7 +9,8 @@ defmodule PhilomenaWeb.Profile.ReportController do
|
||||||
|
|
||||||
plug PhilomenaWeb.FilterBannedUsersPlug
|
plug PhilomenaWeb.FilterBannedUsersPlug
|
||||||
plug PhilomenaWeb.UserAttributionPlug
|
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 PhilomenaWeb.CanaryMapPlug, new: :show, create: :show
|
||||||
|
|
||||||
plug :load_and_authorize_resource,
|
plug :load_and_authorize_resource,
|
||||||
|
|
|
@ -4,7 +4,8 @@ defmodule PhilomenaWeb.RegistrationController do
|
||||||
alias Philomena.Users
|
alias Philomena.Users
|
||||||
alias Philomena.Users.User
|
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 PhilomenaWeb.CompromisedPasswordCheckPlug when action in [:create]
|
||||||
plug :assign_email_and_password_changesets when action in [:edit]
|
plug :assign_email_and_password_changesets when action in [:edit]
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,8 @@ defmodule PhilomenaWeb.ReportController do
|
||||||
#
|
#
|
||||||
# plug PhilomenaWeb.FilterBannedUsersPlug
|
# plug PhilomenaWeb.FilterBannedUsersPlug
|
||||||
# plug PhilomenaWeb.UserAttributionPlug
|
# 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
|
# plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true
|
||||||
|
|
||||||
def create(conn, action, reportable, reportable_type, %{"report" => report_params}) do
|
def create(conn, action, reportable, reportable_type, %{"report" => report_params}) do
|
||||||
|
|
|
@ -9,7 +9,8 @@ defmodule PhilomenaWeb.Topic.Post.ReportController do
|
||||||
|
|
||||||
plug PhilomenaWeb.FilterBannedUsersPlug
|
plug PhilomenaWeb.FilterBannedUsersPlug
|
||||||
plug PhilomenaWeb.UserAttributionPlug
|
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 PhilomenaWeb.CanaryMapPlug, new: :show, create: :show
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,8 @@ defmodule PhilomenaWeb.UnlockController do
|
||||||
|
|
||||||
alias Philomena.Users
|
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
|
def new(conn, _params) do
|
||||||
render(conn, "new.html")
|
render(conn, "new.html")
|
||||||
|
|
|
@ -1,53 +1,29 @@
|
||||||
defmodule PhilomenaWeb.CaptchaPlug do
|
defmodule PhilomenaWeb.CaptchaPlug do
|
||||||
alias Philomena.Captcha
|
alias PhilomenaWeb.ContentSecurityPolicyPlug
|
||||||
import Plug.Conn
|
|
||||||
import Phoenix.Controller
|
|
||||||
|
|
||||||
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
|
def call(conn, _opts) do
|
||||||
case captcha_enabled?() do
|
user = conn.assigns.current_user
|
||||||
true -> maybe_check_captcha(conn, conn.assigns.current_user)
|
|
||||||
false -> conn
|
maybe_assign_csp_headers(conn, user)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_check_captcha(conn, nil) do
|
defp maybe_assign_csp_headers(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
|
|
||||||
conn
|
conn
|
||||||
|> put_status(:multiple_choices)
|
|> ContentSecurityPolicyPlug.permit_source(:script_src, @hcaptcha_url)
|
||||||
|> text("")
|
|> ContentSecurityPolicyPlug.permit_source(:frame_src, @hcaptcha_url)
|
||||||
|
|> ContentSecurityPolicyPlug.permit_source(:style_src, @hcaptcha_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_failure_response(conn, _false) do
|
defp maybe_assign_csp_headers(conn, _user) do
|
||||||
redirect(conn, external: conn.assigns.referrer)
|
conn
|
||||||
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
|
end
|
||||||
end
|
end
|
||||||
|
|
71
lib/philomena_web/plugs/check_captcha_plug.ex
Normal 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
|
|
@ -47,7 +47,7 @@ defmodule PhilomenaWeb.ContentSecurityPolicyPlug do
|
||||||
def permit_source(conn, key, value) when key in @allowed_sources do
|
def permit_source(conn, key, value) when key in @allowed_sources do
|
||||||
conn
|
conn
|
||||||
|> get_config()
|
|> get_config()
|
||||||
|> Keyword.update(key, [], &[value | &1])
|
|> Keyword.update(key, value, &(value ++ &1))
|
||||||
|> set_config(conn)
|
|> set_config(conn)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -486,8 +486,6 @@ defmodule PhilomenaWeb.Router do
|
||||||
resources "/source_changes", Profile.SourceChangeController, only: [:index]
|
resources "/source_changes", Profile.SourceChangeController, only: [:index]
|
||||||
end
|
end
|
||||||
|
|
||||||
resources "/captchas", CaptchaController, only: [:create]
|
|
||||||
|
|
||||||
scope "/posts", Post, as: :post do
|
scope "/posts", Post, as: :post do
|
||||||
resources "/preview", PreviewController, only: [:create]
|
resources "/preview", PreviewController, only: [:create]
|
||||||
end
|
end
|
||||||
|
|
8
lib/philomena_web/templates/captcha/_captcha.html.slime
Normal 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
|
|
@ -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}]"
|
|
|
@ -4,9 +4,7 @@ h1 Resend confirmation instructions
|
||||||
.field
|
.field
|
||||||
= email_input f, :email, placeholder: "Email", class: "input", required: true
|
= email_input f, :email, placeholder: "Email", class: "input", required: true
|
||||||
|
|
||||||
.field
|
= render PhilomenaWeb.CaptchaView, "_captcha.html", name: "confirmation", conn: @conn
|
||||||
= checkbox f, :captcha, class: "js-captcha", value: 0
|
|
||||||
= label f, :captcha, "I am not a robot!"
|
|
||||||
|
|
||||||
div
|
div
|
||||||
= submit "Resend confirmation instructions", class: "button"
|
= submit "Resend confirmation instructions", class: "button"
|
||||||
|
|
|
@ -12,9 +12,7 @@
|
||||||
button.button.button--separate-left type="reset" data-click-hide="#source-form" data-click-show="#image-source"
|
button.button.button--separate-left type="reset" data-click-hide="#source-form" data-click-show="#image-source"
|
||||||
' Cancel
|
' Cancel
|
||||||
|
|
||||||
= if !@conn.assigns.current_user do
|
= render PhilomenaWeb.CaptchaView, "_captcha.html", name: "source", conn: @conn
|
||||||
= checkbox f, :captcha, class: "js-captcha", value: 0
|
|
||||||
= label f, :captcha, "I am not a robot!"
|
|
||||||
|
|
||||||
- else
|
- else
|
||||||
p
|
p
|
||||||
|
|
|
@ -33,9 +33,7 @@
|
||||||
a> href="/pages/tags" tagging guidelines
|
a> href="/pages/tags" tagging guidelines
|
||||||
' before editing tags.
|
' before editing tags.
|
||||||
|
|
||||||
.field
|
= render PhilomenaWeb.CaptchaView, "_captcha.html", name: "tags", conn: @conn
|
||||||
= checkbox f, :captcha, class: "js-captcha", value: 0
|
|
||||||
= label f, :captcha, "I am not a robot!"
|
|
||||||
|
|
||||||
ul.horizontal-list
|
ul.horizontal-list
|
||||||
li
|
li
|
||||||
|
|
|
@ -9,15 +9,6 @@
|
||||||
' Don't post content the artist doesn't want here (or shared in general),
|
' Don't post content the artist doesn't want here (or shared in general),
|
||||||
strong including commercial content.
|
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
|
p
|
||||||
strong
|
strong
|
||||||
' Please check it isn't already here with
|
' Please check it isn't already here with
|
||||||
|
@ -90,5 +81,7 @@
|
||||||
= label f, :anonymous, "Post anonymously"
|
= label f, :anonymous, "Post anonymously"
|
||||||
= checkbox f, :anonymous, class: "checkbox", value: anonymous_by_default?(@conn)
|
= checkbox f, :anonymous, class: "checkbox", value: anonymous_by_default?(@conn)
|
||||||
|
|
||||||
|
= render PhilomenaWeb.CaptchaView, "_captcha.html", name: "image", conn: @conn
|
||||||
|
|
||||||
.actions
|
.actions
|
||||||
= submit "Upload", class: "button", autocomplete: "off", data: [disable_with: "Please wait..."]
|
= submit "Upload", class: "button", autocomplete: "off", data: [disable_with: "Please wait..."]
|
||||||
|
|
|
@ -7,8 +7,6 @@ p
|
||||||
.field
|
.field
|
||||||
= email_input f, :email, class: "input", placeholder: "Email", required: true
|
= email_input f, :email, class: "input", placeholder: "Email", required: true
|
||||||
|
|
||||||
.field
|
= render PhilomenaWeb.CaptchaView, "_captcha.html", name: "password", conn: @conn
|
||||||
= checkbox f, :captcha, class: "js-captcha", value: 0
|
|
||||||
= label f, :captcha, "I am not a robot!"
|
|
||||||
|
|
||||||
= submit "Send instructions to reset password", class: "button"
|
= submit "Send instructions to reset password", class: "button"
|
||||||
|
|
|
@ -28,9 +28,7 @@ h1 Register
|
||||||
= password_input f, :password_confirmation, class: "input", placeholder: "Confirm password", required: true
|
= password_input f, :password_confirmation, class: "input", placeholder: "Confirm password", required: true
|
||||||
= error_tag f, :password_confirmation
|
= error_tag f, :password_confirmation
|
||||||
|
|
||||||
.field
|
= render PhilomenaWeb.CaptchaView, "_captcha.html", name: "registration", conn: @conn
|
||||||
= checkbox f, :captcha, class: "js-captcha", value: 0
|
|
||||||
= label f, :captcha, "I am not a robot!"
|
|
||||||
|
|
||||||
br
|
br
|
||||||
|
|
||||||
|
|
|
@ -60,11 +60,6 @@ p
|
||||||
.block__tab.communication-edit__tab.hidden data-tab="preview"
|
.block__tab.communication-edit__tab.hidden data-tab="preview"
|
||||||
' [Loading preview...]
|
' [Loading preview...]
|
||||||
|
|
||||||
= if !@conn.assigns.current_user do
|
= render PhilomenaWeb.CaptchaView, "_captcha.html", name: "report", conn: @conn
|
||||||
.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.
|
|
||||||
|
|
||||||
= submit "Send Report", class: "button"
|
= submit "Send Report", class: "button"
|
|
@ -4,9 +4,7 @@ h1 Resend unlock instructions
|
||||||
.field
|
.field
|
||||||
= email_input f, :email, placeholder: "Email", class: "input", required: true
|
= email_input f, :email, placeholder: "Email", class: "input", required: true
|
||||||
|
|
||||||
.field
|
= render PhilomenaWeb.CaptchaView, "_captcha.html", name: "unlock", conn: @conn
|
||||||
= checkbox f, :captcha, class: "js-captcha", value: 0
|
|
||||||
= label f, :captcha, "I am not a robot!"
|
|
||||||
|
|
||||||
div
|
div
|
||||||
= submit "Resend unlock instructions", class: "button"
|
= submit "Resend unlock instructions", class: "button"
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
defmodule PhilomenaWeb.CaptchaView do
|
defmodule PhilomenaWeb.CaptchaView do
|
||||||
use PhilomenaWeb, :view
|
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
|
end
|
||||||
|
|