File Coverage

blib/lib/Amon2/Auth/Site/LINE.pm
Criterion Covered Total %
statement 21 94 22.3
branch 0 16 0.0
condition 0 18 0.0
subroutine 7 16 43.7
pod 0 9 0.0
total 28 153 18.3


line stmt bran cond sub pod time code
1             package Amon2::Auth::Site::LINE;
2              
3 1     1   762 use strict;
  1         2  
  1         29  
4 1     1   6 use warnings;
  1         2  
  1         24  
5 1     1   616 use utf8;
  1         15  
  1         6  
6 1     1   559 use URI;
  1         4837  
  1         31  
7 1     1   705 use JSON;
  1         8462  
  1         6  
8 1     1   692 use Mouse;
  1         28776  
  1         4  
9 1     1   1135 use LWP::UserAgent;
  1         40358  
  1         1415  
10            
11             our $VERSION = '0.02';
12              
13 0     0 0   sub moniker { 'line' }
14              
15             has client_id => (
16             is => 'ro',
17             isa => 'Str',
18             required => 1,
19             );
20            
21             has client_secret => (
22             is => 'ro',
23             isa => 'Str',
24             required => 1,
25             );
26            
27             has redirect_uri => (
28             is => 'ro',
29             isa => 'Str',
30             );
31              
32             has state => (
33             is => 'ro',
34             isa => 'Str',
35             );
36              
37             has scope => (
38             is => 'ro',
39             isa => 'ArrayRef',
40             default => sub { [qw(profile)] },
41             );
42              
43             has nonce => (
44             is => 'ro',
45             isa => 'Str',
46             );
47              
48             has prompt => (
49             is => 'ro',
50             isa => 'Str',
51             );
52              
53             has max_age => (
54             is => 'ro',
55             isa => 'Int',
56             );
57              
58             has ui_locales => (
59             is => 'ro',
60             isa => 'Str',
61             );
62              
63             has bot_prompt => (
64             is => 'ro',
65             isa => 'Str',
66             );
67              
68             has user_info => (
69             is => 'rw',
70             isa => 'Bool',
71             default => 1,
72             );
73              
74             has state_session_key => (
75             is => 'ro',
76             isa => 'Str',
77             default => 'line_login_state',
78             );
79              
80             has nonce_session_key => (
81             is => 'ro',
82             isa => 'Str',
83             default => 'line_login_nonce',
84             );
85              
86             has authorize_url => (
87             is => 'ro',
88             isa => 'Str',
89             default => 'https://access.line.me/oauth2/v2.1/authorize',
90             );
91              
92             has access_token_url => (
93             is => 'ro',
94             isa => 'Str',
95             default => 'https://api.line.me/oauth2/v2.1/token',
96             );
97              
98             has verify_url => (
99             is => 'ro',
100             isa => 'Str',
101             default => 'https://api.line.me/oauth2/v2.1/verify',
102             );
103              
104             has profile_url => (
105             is => 'ro',
106             isa => 'Str',
107             default => 'https://api.line.me/v2/profile',
108             );
109              
110             has ua => (
111             is => 'ro',
112             isa => 'LWP::UserAgent',
113             lazy => 1,
114             default => sub {
115             LWP::UserAgent->new(agent => "Amon2::Auth::Site::LINE/$VERSION");
116             },
117             );
118              
119             sub auth_uri {
120 0     0 0   my($self, $c, $callback_uri) = @_;
121              
122             # required parameters
123 0   0       my $redirect_uri = $self->redirect_uri // $callback_uri;
124             my %params = (
125             response_type => 'code',
126             client_id => $self->client_id,
127 0           scope => join(' ', @{$self->scope}),
  0            
128             redirect_uri => $redirect_uri,
129             state => $self->get_state($c),
130             );
131              
132             # optional parameters
133 0           $params{nonce} = $self->get_nonce($c);
134              
135 0           for my $key (qw(prompt max_age ui_locales bot_prompt)) {
136 0           my $value = $self->$key;
137 0 0         if (defined $value) {
138 0           $params{$key} = $value;
139             }
140             }
141              
142 0           my $auth_uri = URI->new($self->authorize_url);
143 0           $auth_uri->query_form(%params);
144              
145 0           return $auth_uri->as_string;
146             }
147              
148             sub callback {
149 0     0 0   my($self, $c, $callback) = @_;
150              
151             # state mismatch
152 0 0         if ($c->req->param('state') ne $self->get_state($c)) {
153 0           return $callback->{on_error}->('state parameter mismatch');
154             }
155              
156             # access denied
157 0 0         if ($c->req->param('error')) {
158 0           return $callback->{on_error}->($c->req->param('error_description'));
159             }
160            
161 0           my @args = ();
162              
163             # getting an access token
164 0           my $token_data;
165             {
166 0   0       my $redirect_uri = $self->redirect_uri // do { # it should be me
  0            
167 0           my $current_uri = $c->req->uri;
168 0           $current_uri->query(undef);
169 0           $current_uri->as_string;
170             };
171 0           my $res = $self->ua->post($self->access_token_url => +{
172             grant_type => 'authorization_code',
173             code => $c->req->param('code'),
174             redirect_uri => $redirect_uri,
175             client_id => $self->client_id,
176             client_secret => $self->client_secret,
177             });
178 0 0         unless ($res->is_success) {
179 0           warn $res->decoded_content;
180 0           return $callback->{on_error}->($res->status_line);
181             }
182              
183 0           $token_data = decode_json($res->content);
184             }
185              
186             # verify access token
187 0           my $verify_data;
188             {
189 0           my $uri = URI->new($self->verify_url);
  0            
190 0           $uri->query_form(access_token => $token_data->{access_token});
191              
192 0           my $res = $self->ua->get($uri->as_string);
193 0 0         unless ($res->is_success) {
194 0           warn $res->decoded_content;
195 0           return $callback->{on_error}->($res->status_line);
196             }
197              
198 0           $verify_data = decode_json($res->content);
199 0 0         if ($verify_data->{client_id} ne $self->client_id) {
200 0           return $callback->{on_error}->('client_id mismatch');
201             }
202              
203 0           push @args, $token_data->{access_token};
204             }
205              
206              
207             # get user profile
208 0 0         if ($self->user_info) {
209 0           my $uri = URI->new($self->profile_url);
210             my $res = $self->ua->get(
211             $uri->as_string,
212             Authorization => 'Bearer ' . $token_data->{access_token},
213 0           );
214 0 0         $res->is_success or do {
215 0           warn $res->decoded_content;
216 0           return $callback->{on_error}->($res->decoded_content);
217             };
218 0           my $user = decode_json($res->content);
219 0           push @args, $user;
220             }
221              
222 0           $self->clear_state($c);
223 0           $self->clear_nonce($c);
224              
225 0           $callback->{on_finished}->(@args);
226             }
227              
228             sub get_state {
229 0     0 0   my($self, $c) = @_;
230 0   0       my $state = $self->state // $c->session->get($self->state_session_key) // do {
      0        
231 0           require String::Random;
232 0           String::Random->new->randregex('[a-zA-Z0-9]{16}');
233             };
234 0           $self->set_state($c, $state);
235 0           return $state;
236             }
237              
238             sub set_state {
239 0     0 0   my($self, $c, $state) = @_;
240 0           return $c->session->set($self->state_session_key => $state);
241             }
242              
243             sub clear_state {
244 0     0 0   my($self, $c) = @_;
245 0           return $c->session->remove($self->state_session_key);
246             }
247              
248             sub get_nonce {
249 0     0 0   my($self, $c) = @_;
250 0   0       my $nonce = $self->nonce // $c->session->get($self->nonce_session_key) // do {
      0        
251 0           require String::Random;
252 0           String::Random->new->randregex('[a-zA-Z0-9]{16}');
253             };
254 0           $self->set_nonce($c, $nonce);
255 0           return $nonce;
256             }
257              
258             sub set_nonce {
259 0     0 0   my($self, $c, $nonce) = @_;
260 0           return $c->session->set($self->nonce_session_key => $nonce);
261             }
262              
263             sub clear_nonce {
264 0     0 0   my($self, $c) = @_;
265 0           return $c->session->remove($self->nonce_session_key);
266             }
267            
268             1;
269             __END__