add captcha, controller, images

This commit is contained in:
byte[] 2019-11-13 11:28:02 -05:00
parent 7784c09b3e
commit dd1c1e61f5
23 changed files with 176 additions and 3 deletions

View file

@ -1,2 +1,3 @@
alias Philomena.{Repo, Comments.Comment, Posts.Post, Images.Image, Tags.Tag, Users.User}
import Ecto.Query
import Ecto.Changeset

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View file

@ -14,7 +14,8 @@ defmodule Philomena.Application do
PhilomenaWeb.Endpoint,
# Starts a worker by calling: Philomena.Worker.start_link(arg)
# {Philomena.Worker, arg},
Pow.Store.Backend.MnesiaCache
Pow.Store.Backend.MnesiaCache,
{Redix, name: :redix}
]
# See https://hexdocs.pm/elixir/Supervisor.html

130
lib/philomena/captcha.ex Normal file
View file

@ -0,0 +1,130 @@
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) 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
end

View file

@ -0,0 +1,11 @@
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

@ -67,6 +67,7 @@ defmodule PhilomenaWeb.Router do
end
resources "/filters", FilterController
resources "/profiles", ProfileController, only: [:show]
resources "/captchas", CaptchaController, only: [:create]
get "/:id", ImageController, :show
end

View file

@ -0,0 +1,20 @@
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"

View file

@ -19,6 +19,10 @@ h1 Sign in
= checkbox f, :persistent_session
= label f, :persistent_session, "Remember me"
.field
= checkbox f, :captcha, class: "js-captcha", value: 0
= label f, :captcha, "I am not a robot!"
= submit "Sign in", class: "button"
p

View file

@ -0,0 +1,3 @@
defmodule PhilomenaWeb.CaptchaView do
use PhilomenaWeb, :view
end

View file

@ -54,7 +54,8 @@ defmodule Philomena.MixProject do
{:canary, "~> 1.1.1"},
{:scrivener_ecto, "~> 2.0"},
{:pbkdf2, "~> 2.0"},
{:qrcode, "~> 0.1.5"}
{:qrcode, "~> 0.1.5"},
{:redix, "~> 0.10.2"}
]
end

View file

@ -43,6 +43,7 @@
"pow": {:hex, :pow, "1.0.13", "5ca3e8d9fecca037bfb0ea3b8dde070cc319746498e844d59fc209d461b0d426", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.3.0 or ~> 1.4.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, ">= 2.0.0 and <= 3.0.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, ">= 1.5.0 and < 2.0.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm"},
"qrcode": {:hex, :qrcode, "0.1.5", "551271830515c150f34568345b060c625deb0e6691db2a01b0a6de3aafc93886", [:mix], [], "hexpm"},
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
"redix": {:hex, :redix, "0.10.2", "a9eabf47898aa878650df36194aeb63966d74f5bd69d9caa37babb32dbb93c5d", [:mix], [{:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
"retry": {:hex, :retry, "0.13.0", "bb9b2713f70f39337837852337ad280c77662574f4fb852a8386c269f3d734c4", [:mix], [], "hexpm"},
"scrivener": {:hex, :scrivener, "2.7.0", "fa94cdea21fad0649921d8066b1833d18d296217bfdf4a5389a2f45ee857b773", [:mix], [], "hexpm"},
"scrivener_ecto": {:hex, :scrivener_ecto, "2.2.0", "53d5f1ba28f35f17891cf526ee102f8f225b7024d1cdaf8984875467158c9c5e", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:scrivener, "~> 2.4", [hex: :scrivener, repo: "hexpm", optional: false]}], "hexpm"},