From abb4c9ae95ac83aee91cdee69d6606ff137a0786 Mon Sep 17 00:00:00 2001 From: amn Date: Thu, 27 Aug 2015 11:21:15 +0600 Subject: Initial commit --- .gitignore | 2 + LICENSE | 22 +++++++++++ README.md | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++ config/config.exs | 7 ++++ lib/recaptcha.ex | 54 +++++++++++++++++++++++++++ lib/template.html.eex | 35 ++++++++++++++++++ mix.exs | 37 +++++++++++++++++++ mix.lock | 3 ++ 8 files changed, 260 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 config/config.exs create mode 100644 lib/recaptcha.ex create mode 100644 lib/template.html.eex create mode 100644 mix.exs create mode 100644 mix.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..47df665 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/_build +erl_crash.dump diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a492b9b --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2015 Mikhail Alekseev + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9c31255 --- /dev/null +++ b/README.md @@ -0,0 +1,100 @@ +# Recaptcha + +A simple Elixir package for implementing [ReCaptcha] v2 in [Phoenix] applications. + +[ReCaptcha]: http://www.google.com/recaptcha +[Phoenix]: http://www.phoenixframework.org/ + +## Installation + +Set as a dependency in the and ensure it is running with your app: + +1) Add as a dependency to the mix.exs file + +```elixir + defp deps do + [ + # other deps + {:recaptcha, "~> 0.0.1"}, + {:ibrowse, github: "cmullaparthi/ibrowse", tag: "v4.1.2"}, + # other deps + ] + end +``` + +2) Add to your application list + +```elixir +def application do + [ + # ... + applications: [:phoenix, :recaptcha] + # ... + ] +end +``` + +Get your project's dependencies: + +```bash +$ mix deps.get +``` + +## Config + +In your application's config.exs : + +```elixir +config :recaptcha, + api_config: %{ verify_url: "https://www.google.com/recaptcha/api/siteverify", + public_key: "YOUR_PUBLIC_KEY", + private_key: "YOUR_PRIVATE_KEY" } +``` + +## Usage + +### View + +In a template + +```html +
+ ... + <%= raw Recaptcha.display %> + ... +
+``` + +Display method accepts a keyword list with 2 possible options: + +* `noscript` -> Set to true to render noscript code +* `public_key` -> The key put in the `data-sitekey` reCaptcha div attribute, default is the public key set in the config file + +Pass these parameters like this: + +```elixir +... +<%= raw Recaptcha.display(noscript: true, public_key: "Public key") %> +... +``` + +### Controller + +In a controller + +```elixir + +def create(conn, params) do + # some code + case Recaptcha.verify(conn.remote_ip, params["g-recaptcha-response"]) do + :ok -> do_something + :error -> handle_error + end +end + +``` + +`verify` method also accepts a keyword list as the third parameter with also 2 possible options: + +* `public_key` -> A key to use in the http request to the recaptcha api, default is the private key set in the config file +* `timeout` -> Time period in ms to wait for a response from google api, default is 3000 diff --git a/config/config.exs b/config/config.exs new file mode 100644 index 0000000..3698024 --- /dev/null +++ b/config/config.exs @@ -0,0 +1,7 @@ +use Mix.Config + +config :recaptcha, :api_config, + %{ verify_url: "https://www.google.com/recaptcha/api/siteverify", + public_key: "YOUR PUBLIC KEY", + private_key: "YOUR PRIVATE KEY" + } diff --git a/lib/recaptcha.ex b/lib/recaptcha.ex new file mode 100644 index 0000000..c9abf38 --- /dev/null +++ b/lib/recaptcha.ex @@ -0,0 +1,54 @@ +defmodule Recaptcha do + require Elixir.EEx + + @secret_key_errors ~w(missing-input-secret invalid-input-secret) + + EEx.function_from_file :defp, :render_template, "lib/template.html.eex", [:assigns] + + def display(options \\ []) do + public_key = options[:public_key] || config.public_key + render_template(public_key: public_key, options: options) + end + + def verify(remote_ip, response, options \\ []) + + def verify(remote_ip, response, options) when is_tuple(remote_ip) do + verify(:inet_parse.ntoa(remote_ip), response, options) + end + + def verify(remote_ip, response, options) do + case api_response(remote_ip, response, options) do + %{"success" => true} -> + :ok + %{"success" => false, "error-codes" => error_codes} -> + handle_error_codes(error_codes) + %{"success" => false} -> + :error + end + end + + defp api_response(remote_ip, response, options) do + private_key = options[:private_key] || config.private_key + timeout = options[:timeout] || 3000 + body_content = URI.encode_query(%{"remoteip" => to_string(remote_ip), + "response" => response, + "secret" => private_key}) + headers = ["Content-type": "application/x-www-form-urlencoded"] + options = [body: body_content, headers: headers, timeout: timeout] + HTTPotion.post(config.verify_url, options).body |> Poison.decode! + end + + defp config do + Application.get_env(:recaptcha, :api_config) + end + + defp handle_error_codes(error_codes) do + if Enum.any?(error_codes, fn(code) -> Enum.member?(@secret_key_errors, code) end) do + raise RuntimeError, + message: "reCaptcha API has declined the private key. Please make sure you've set the correct private key" + else + :error + end + end + +end diff --git a/lib/template.html.eex b/lib/template.html.eex new file mode 100644 index 0000000..c9c5385 --- /dev/null +++ b/lib/template.html.eex @@ -0,0 +1,35 @@ + + +
+
+ +<%= if @options[:noscript] do %> + + + +<% end %> diff --git a/mix.exs b/mix.exs new file mode 100644 index 0000000..780ed45 --- /dev/null +++ b/mix.exs @@ -0,0 +1,37 @@ +defmodule Recaptcha.Mixfile do + use Mix.Project + + def project do + [app: :recaptcha, + version: "0.0.1", + elixir: "~> 1.0.0", + description: description, + deps: deps, + package: package] + end + + def application do + [applications: [:logger, :httpotion]] + end + + defp description do + """ + A simple reCaptcha package for Phoenix applications. + """ + end + + defp deps do + [ + {:ibrowse, github: "cmullaparthi/ibrowse", tag: "v4.1.2"}, + {:httpotion, "~> 2.1.0"}, + {:poison, "~> 1.5"} + ] + end + + defp package do + [files: ["lib", "mix.exs", "README.md", "LICENSE"], + contributors: ["Alekseev Mikhail"], + licenses: ["MIT"], + links: %{"GitHub" => "https://github.com/JustMikey/recaptcha"}] + end +end diff --git a/mix.lock b/mix.lock new file mode 100644 index 0000000..1109ea8 --- /dev/null +++ b/mix.lock @@ -0,0 +1,3 @@ +%{"httpotion": {:hex, :httpotion, "2.1.0"}, + "ibrowse": {:git, "git://github.com/cmullaparthi/ibrowse.git", "ea3305d21f37eced4fac290f64b068e56df7de80", [tag: "v4.1.2"]}, + "poison": {:hex, :poison, "1.5.0"}} -- cgit v1.2.1