-
Notifications
You must be signed in to change notification settings - Fork 5
/
mod_recaptcha.erl
104 lines (87 loc) · 3.39 KB
/
mod_recaptcha.erl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
%% @author François Cardinaux, CH 1207 Geneva (http://bit.ly/qTaona)
%% and the owner of recaptcha-erlang (http://code.google.com/p/recaptcha-erlang/)
%% @copyright 2011 François Cardinaux
%% @date 2011-09-18
%% @doc Recaptcha module
%% @todo Implement the use of the Ajax API (http://code.google.com/apis/recaptcha/docs/display.html)
-module(mod_recaptcha).
-author("François Cardinaux <[email protected]>").
-mod_title("Recaptcha").
-mod_description("Recaptcha module.").
-export([observe_signup_check/3]).
-export([check_recaptcha/1]).
-include_lib("zotonic.hrl").
%% @doc Recaptcha check on a signup
%% This function is called by mod_signup:check_signup/3, where the instruction
%% z_notifier:foldl(signup_check, ...) calls **all** signup_check observers,
%% including this one.
observe_signup_check(signup_check, {ok, Props, SignupProps}, Context) ->
case m_recaptcha:is_enabled(Context) of
false ->
{ok, Props, SignupProps};
_ ->
case check_recaptcha(Context) of
ok ->
{ok, Props, SignupProps};
{error, _} = Error ->
Error
end
end.
%% @doc This function calls the reCAPTCHA API and verifies that the response
%% corresponds to the challenge
%% @spec check_recaptcha(record()) -> ok | {error, Reason}
check_recaptcha(Context) ->
RemoteIP = get_remote_ip(Context),
Challenge = z_context:get_q("recaptcha_challenge_field", Context),
Response = z_context:get_q("recaptcha_response_field", Context),
% Explanation:
% * http://erlangexamples.com/2009/02/24/how-to-make-http-post/
% * http://code.google.com/p/recaptcha-erlang/
inets:start(),
Data = string:join(
[
string:join(["privatekey", mochiweb_util:quote_plus(m_recaptcha:private_key(Context))], "="),
string:join(["remoteip", mochiweb_util:quote_plus(RemoteIP)], "="),
string:join(["challenge", mochiweb_util:quote_plus(Challenge)], "="),
string:join(["response", mochiweb_util:quote_plus(Response)], "=")
], "&"),
HttpResponse =
httpc:request(
post,
{
get_recaptcha_verify_url(),
[],
"application/x-www-form-urlencoded",
Data
},
[], []),
case HttpResponse of
{ok, Result} ->
[FirstLine|NextLines] = case Result of
{_Status, _Headers, Body} -> string:tokens(Body, "\r\n");
{_Status, Body} -> string:tokens(Body, "\r\n")
end,
case FirstLine of
"true" ->
ok;
_ ->
{error, lists:flatten(NextLines)}
end;
{error, _} = Error ->
Error
end.
%% Support function
get_recaptcha_verify_url() ->
"http://www.google.com/recaptcha/api/verify".
get_remote_ip(Context) ->
% This instruction is wrapped in a try-catch statement because it relies on
% wrq:peer/1, which may theoretically fail, if the function guard isn't
% respected.
try m_req:get(peer, Context) of
IP ->
IP
catch
Error:Reason ->
io:format("Unable to find remote IP: ~p, ~p~n", [Error, Reason]),
"127.0.0.1"
end.