File Coverage

blib/lib/Mojolicious/Plugin/ReCAPTCHAv2.pm
Criterion Covered Total %
statement 74 94 78.7
branch 18 30 60.0
condition 12 23 52.1
subroutine 7 7 100.0
pod 1 1 100.0
total 112 155 72.2


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::ReCAPTCHAv2;
2             $Mojolicious::Plugin::ReCAPTCHAv2::VERSION = '1.06';
3             # vim:syntax=perl:tabstop=4:number:noexpandtab:
4              
5 6     6   73885 use Mojo::Base 'Mojolicious::Plugin';
  6         197661  
  6         48  
6              
7             # ABSTRACT: use Googles "No CAPTCHA reCAPCTHA" (reCAPTCHA v2) service in Mojolicious apps
8              
9 6     6   2767 use Mojo::JSON qw();
  6         23235  
  6         109  
10 6     6   636 use Mojo::UserAgent qw();
  6         255552  
  6         9527  
11              
12             has conf => sub { +{} };
13             has ua => sub { Mojo::UserAgent->new()->max_redirects( 0 ) };
14              
15              
16             sub register {
17 5     5 1 209 my $plugin = shift;
18 5         10 my $app = shift;
19 5   50     22 my $conf = shift || {};
20              
21             die ref( $plugin ), ": need sitekey and secret!\n"
22 5 50 33     39 unless $conf->{'sitekey'} and $conf->{'secret'};
23              
24 5   50     31 $conf->{'api_url'} //= 'https://www.google.com/recaptcha/api/siteverify';
25 5   50     26 $conf->{'api_timeout'} //= 10;
26              
27 5         19 $plugin->conf( $conf );
28 5         54 $plugin->ua->request_timeout( $conf->{'api_timeout'} );
29              
30             $app->helper(
31             recaptcha_get_html => sub {
32 3     3   53486 my $c = shift;
33 3 100       11 my $language = $_[0] ? shift : undef;
34              
35 3         16 my %data_attr = map { $_ => $plugin->conf->{$_} } grep { index( $_, 'api_' ) != 0 } keys %{ $plugin->conf };
  12         52  
  18         62  
  3         17  
36              
37             # Never expose this!
38 3         26 delete $data_attr{'secret'};
39              
40 3         14 my $hl = '';
41 3 100 66     25 if ( defined $language and $language ) {
    50          
42 1         2 $hl = $language;
43             }
44             elsif ( exists $data_attr{'language'} ) {
45 0         0 $hl = delete $data_attr{'language'};
46             }
47              
48 3         22 my $output = '';
49 3         16 my $template = q|
50            
data-<%= $k %>="<%= $attr->{$k} %>"<% } %>>
|;
51              
52 3         42 return $c->render_to_string(
53             handler => 'ep',
54             inline => $template,
55             hl => $hl,
56             attr => \%data_attr,
57             );
58             }
59 5         164 );
60              
61             $app->helper(
62             recaptcha_verify => sub {
63 3     3   47986 my $c = shift;
64              
65 3         8 my $cb = shift;
66 3 100 66     48 unless ( defined( $cb ) and ref( $cb ) eq 'CODE' ) {
67 2         16 $cb = '';
68             }
69              
70             my %verify_params = (
71             remoteip => $c->tx->remote_address,
72             response => ( $c->req->param( 'g-recaptcha-response' ) || '' ),
73 3   50     18 secret => $plugin->conf->{'secret'},
74             );
75              
76 3         1095 my $url = $plugin->conf->{'api_url'};
77              
78             my $response_handler = sub {
79 3         102923 my ( $ua, $tx ) = @_;
80              
81 3         14 my ( $verified, $err ) = ( 0, [] );
82 3 50       16 if ( my $txerr = $tx->error ) {
83 0         0 my $txt = 'Retrieving captcha verifcation failed';
84 0 0       0 $txt .= ' (HTTP ' . $txerr->{'code'} . ')' if $txerr->{'code'};
85              
86 0         0 $c->app->log->error( $txt . ': ' . $txerr->{'message'} );
87 0         0 $c->app->log->error( 'Request was: ' . $tx->req->to_string );
88              
89 0         0 ( $verified, $err ) = ( 0, ['x-http-communication-failed'] );
90             }
91             else {
92 3         91 my $json = '';
93 3         9 eval { $json = Mojo::JSON::decode_json( $tx->res->body ); };
  3         15  
94              
95 3 50       1196 if ( $@ ) {
96 0         0 $c->app->log->error( 'Decoding JSON response failed: ' . $@ );
97 0         0 $c->app->log->error( 'Request was: ' . $tx->req->to_string );
98 0         0 $c->app->log->error( 'Response was: ' . $tx->res->to_string );
99              
100 0         0 ( $verified, $err ) = ( 0, ['x-unparseable-data-received'] );
101             }
102             else {
103 3 50       54 unless ( $json->{'success'} == Mojo::JSON->true ) {
104 3   50     198 @{$err} = @{ $json->{'error-codes'} // [] };
  3         12  
  3         31  
105             }
106 3         18 $verified = $json->{'success'};
107             }
108             }
109              
110 3 100       21 return $cb->( $verified, $err ) if $cb;
111 2         63 return ( $verified, $err );
112 3         37 };
113              
114 3 100       26 if ( $cb ) {
115 1         5 return $plugin->ua->post( $url, form => \%verify_params, $response_handler );
116             }
117             else {
118 2         13 my $tx = $plugin->ua->post( $url, form => \%verify_params );
119 2         172054 return $response_handler->( $plugin->ua, $tx );
120             }
121             }
122 5         824 );
123              
124             $app->helper(
125             recaptcha_verify_p => sub {
126 1     1   17888 my $c = shift;
127              
128             my %verify_params = (
129             remoteip => $c->tx->remote_address,
130             response => ( $c->req->param( 'g-recaptcha-response' ) || '' ),
131 1   50     5 secret => $plugin->conf->{'secret'},
132             );
133              
134 1         340 my $url = $plugin->conf->{'api_url'};
135              
136             # Async request using promises
137 1         15 require Mojo::Promise;
138 1         12 my $p = Mojo::Promise->new();
139             $plugin->ua->post(
140             $url => form => \%verify_params,
141             sub {
142 1         101501 my ( $ua, $tx ) = @_;
143              
144 1 50       6 if ( my $err = $tx->error ) {
145 0         0 my $txt = 'Retrieving captcha verification failed';
146 0 0       0 $txt .= ' (HTTP ' . $err->{'code'} . ')' if $err->{'code'};
147              
148 0         0 $c->app->log->error( $txt . ': ' . $err->{'message'} );
149 0         0 $c->app->log->error( 'Request was: ' . $tx->req->to_string );
150 0         0 return $p->reject( ['x-http-communication-failed'] );
151             }
152              
153 1         25 my $res = $tx->res;
154 1         6 my $json = eval { $res->json };
  1         41  
155              
156 1 50       349 if ( not defined $json ) {
157 0         0 $c->app->log->error( 'Decoding JSON response failed: ' . $@ );
158 0         0 $c->app->log->error( 'Request was: ' . $tx->req->to_string );
159 0         0 $c->app->log->error( 'Response was: ' . $tx->res->to_string );
160 0         0 return $p->reject( ['x-unparseable-data-received'] );
161             }
162              
163 1 50       43 unless ( $json->{'success'} ) {
164 1   50     22 return $p->reject( $json->{'error-codes'} // [] );
165             }
166              
167 0         0 return $p->resolve( $json->{'success'} );
168             }
169 1         71 );
170              
171 1         1621 return $p;
172             }
173 5         526 );
174              
175 5         414 return;
176             } ## end sub register
177              
178             1;
179              
180             __END__