File Coverage

blib/lib/WebPrototypes/ResetPass.pm
Criterion Covered Total %
statement 73 77 94.8
branch 10 12 83.3
condition 5 14 35.7
subroutine 18 21 85.7
pod 7 7 100.0
total 113 131 86.2


line stmt bran cond sub pod time code
1 1     1   349734 use strict;
  1         4  
  1         33  
2 1     1   5 use warnings;
  1         2  
  1         53  
3              
4             package WebPrototypes::ResetPass;
5             BEGIN {
6 1     1   19 $WebPrototypes::ResetPass::VERSION = '0.002';
7             }
8 1     1   10 use parent qw(Plack::Component);
  1         1  
  1         12  
9 1     1   937 use Plack::Request;
  1         79177  
  1         38  
10 1     1   1281 use URL::Encode 'url_encode_utf8';
  1         5850  
  1         62  
11 1     1   934 use String::Random 'random_regex';
  1         3087  
  1         64  
12              
13 1     1   811 use Email::Sender::Simple qw(sendmail);
  1         217205  
  1         8  
14 1     1   278 use Email::Simple;
  1         1  
  1         20  
15 1     1   4 use Email::Simple::Creator;
  1         2  
  1         18  
16              
17 1     1   18 use 5.0100;
  1         6  
  1         802  
18              
19 0     0 1 0 sub find_user { die 'find_user needs to be implemented in subclass' }
20              
21 0     0 1 0 sub update_user{ die 'update_user needs to be implemented in subclass' }
22              
23             sub wrap_text{
24 7     7 1 11 my( $self, $text ) = @_;
25 7         118 return "$text";
26             }
27              
28             sub build_reply{
29 7     7 1 14 my( $self, $text ) = @_;
30 7         38 return [ 200, [ 'Content-Type' => 'text/html' ], [ $self->wrap_text( $text ) ] ];
31             }
32              
33             sub call {
34 7     7 1 724512 my($self, $env) = @_;
35 7         23 my $path = $env->{PATH_INFO};
36              
37 7 100       30 if( $path eq '/reset' ){
38 3         21 return $self->_reset( $env );
39             }
40 4         21 return $self->_index( $env );
41             }
42              
43             sub _index {
44 4     4   10 my ( $self, $env ) = @_;
45 4         42 my $req = Plack::Request->new( $env );
46 4 100       167 if( $req->method eq 'POST' ){
47 2         22 my $username = $req->param( 'username' );
48 2         1165 my( $user, $email ) = $self->find_user( $username );
49 2 100       17 if( !$user ){
50 1         4 return $self->build_reply( "User not found" );
51             }
52             else{
53 1         6 my $pass_token = random_regex( '\w{40}' );
54 1         245 $self->update_user( $user, { pass_token => $pass_token });
55 1         20 $self->_send_pass_token( $env, $user, $username, $email, $pass_token );
56 1         2261 return $self->build_reply( "Email sent" );
57             }
58             }
59 2         35 return $self->build_reply( <
60            
61             Username or email:
62            
63            
64             END
65              
66             }
67              
68             sub build_email {
69 1     1 1 53 my( $self, $to, $reset_url ) = @_;
70 1         25 return Email::Simple->create(
71             header => [
72             To => $to,
73             From => 'root@localhost',
74             Subject => "Password reset",
75             ],
76             body => $reset_url,
77             );
78             }
79              
80             sub send_mail {
81 0     0 1 0 my( $self, $mail ) = @_;
82 0         0 sendmail( $mail );
83             }
84              
85             sub _send_pass_token {
86 1     1   4 my( $self, $env, $user, $username, $email, $pass_token ) = @_;
87 1 0 50     17 my $my_server = $env->{HTTP_ORIGIN} //
      0        
      33        
      33        
88             ( $env->{'psgi.url_scheme'} // 'http' ) . '://' .
89             ( $env->{HTTP_HOST} //
90             $env->{SERVER_NAME} .
91             ( $env->{SERVER_PORT} && $env->{SERVER_PORT} != 80 ? ':' . $env->{SERVER_PORT} : '' )
92             );
93 1         22 my $reset_url = URI->new( $my_server );
94 1         130 $reset_url->path( $env->{SCRIPT_NAME} . '/reset' );
95 1         108 $reset_url->query_form( name => $username, token => $pass_token );
96 1         68 $self->send_mail( $self->build_email( $email, $reset_url ), $pass_token );
97             }
98              
99             sub _reset {
100 3     3   8 my ( $self, $env, ) = @_;
101 3         32 my $req = Plack::Request->new( $env );
102 3         41 my $name = $req->param( 'name' );
103 3         1040 my $token = $req->param( 'token' );
104 3         44 my( $user, $email, $pass_token ) = $self->find_user( $name );
105 3 100 66     41 if( !( $user && $pass_token eq $token ) ){
106 1         6 return $self->build_reply( 'Token invalid' );
107             }
108             else{
109 2 100       9 if( $req->method eq 'POST' ){
110 1         8 $self->update_user( $user, { pass_token => undef, password => $req->param( 'password' ) } );
111 1         31 return $self->build_reply( 'Password reset' );
112             }
113             else{
114 1         15 my $encoded_name = url_encode_utf8( $name );
115 1         86 my $encoded_token = url_encode_utf8( $pass_token );
116 1         19 return $self->build_reply( <
117            
118             New password:
119            
120            
121            
122            
123             END
124             }
125             }
126             }
127              
128              
129             1;
130              
131              
132              
133             =pod
134              
135             =head1 NAME
136              
137             WebPrototypes::ResetPass - (Experimental) Plack application for sending a 'Reset password link' via email
138              
139             =head1 VERSION
140              
141             version 0.002
142              
143             =head1 SYNOPSIS
144              
145             # connecting with DBIx::Class
146             {
147             package My::ResetPass;
148             use parent 'WebPrototypes::ResetPass';
149             use Plack::Util::Accessor qw( schema );
150              
151             sub find_user {
152             my( $self, $name ) = @_;
153             my $user = $schema->resultset( 'User' )->search({ username => $name })->next;
154             return $user, $user->email, $user->pass_token if $user;
155             return;
156             }
157              
158             sub update_user {
159             my( $self, $user, $attrs ) = @_;
160             $user->update( $attrs );
161             }
162              
163             }
164              
165             use Plack::Builder;
166              
167             my $app = My::ResetPass->new( schema => $schema );
168              
169             builder {
170             mount "/forgotten_pass" => builder {
171             $app->to_app;
172             };
173             };
174              
175             =head1 DESCRIPTION
176              
177             This application implements the common reset forgotten password mechanism
178             in a storage independent way. The examples here are with DBIx::Class
179             but they can be easily ported to other storage layers.
180              
181             It has two pages. First page where the user enters his login details and
182             if they are correct an email with a link (with a random verification token)
183             to the password reset page is sent.
184             Second page - the password reset page - checks the token - and lets the user
185             to choose a new password.
186              
187             This application uses the Template Method design pattern.
188              
189             =head2 PURE VIRTUAL METHODS
190              
191             These methods need to be overriden in subclass.
192              
193             =over 4
194              
195             =item find_user ( name )
196              
197             Should return a following tuple
198             $user, $user_email, $verification_token
199              
200             The C<$user> is user object or user id - passed to the C method
201              
202             =item update_user ( user, params )
203              
204             Should update the user object with params.
205             It is used for saving the new password and verification token.
206              
207             =back
208              
209             =head2 VIRTUAL METHODS
210              
211             These methods have defaults - but should probably be overriden anyway.
212              
213             =over 4
214              
215             =item wrap_text ( text )
216              
217             Should return the html page containing the passed text fragment. By default it just adds
218             the html and body tags.
219              
220             =item build_reply ( page_body )
221              
222             Should return the PSGI response data structure.
223              
224             =item build_email ( to_address, link_to_the_reset_page )
225              
226             Should create the email containing the link.
227              
228             =item send_mail ( mail )
229              
230             Should send the mail (created by build_mail).
231              
232             =back
233              
234             =head2 OTHER METHODS
235              
236             =over 4
237              
238             =item call ( env )
239              
240             =back
241              
242             =head1 SEE ALSO
243              
244             L
245             L
246              
247             =head1 AUTHOR
248              
249             Zbigniew Lukasiak
250              
251             =head1 COPYRIGHT AND LICENSE
252              
253             This software is Copyright (c) 2011 by Zbigniew Lukasiak .
254              
255             This is free software, licensed under:
256              
257             The Artistic License 2.0 (GPL Compatible)
258              
259             =cut
260              
261              
262             __END__