From a8dfc34159e9f48e53f2430368f5143d002cdb35 Mon Sep 17 00:00:00 2001 From: Blou Date: Thu, 26 Sep 2013 14:24:29 -0400 Subject: [PATCH] Add forgot password link to OPAC --- C4/Auth.pm | 4 +- C4/Members.pm | 82 ++++++- .../prog/en/modules/admin/preferences/opac.pref | 9 +- koha-tmpl/opac-tmpl/prog/en/modules/opac-auth.tt | 3 + koha-tmpl/opac-tmpl/prog/en/modules/opac-main.tt | 8 +- .../prog/en/modules/opac-password-recovery.tt | 129 +++++++++++ .../prog/en/modules/opac-send-password-recovery.tt | 13 ++ opac/opac-main.pl | 1 + opac/opac-password-recovery.pl | 224 ++++++++++++++++++++ 9 files changed, 468 insertions(+), 5 deletions(-) create mode 100644 koha-tmpl/opac-tmpl/prog/en/modules/opac-password-recovery.tt create mode 100644 koha-tmpl/opac-tmpl/prog/en/modules/opac-send-password-recovery.tt create mode 100755 opac/opac-password-recovery.pl diff --git a/C4/Auth.pm b/C4/Auth.pm index 1e17e59..428aaea 100644 --- a/C4/Auth.pm +++ b/C4/Auth.pm @@ -1020,8 +1020,10 @@ sub checkauth { PatronSelfRegistrationDefaultCategory => C4::Context->preference("PatronSelfRegistrationDefaultCategory"), persona => C4::Context->preference("Persona"), opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'}, + OpacResetPassword => C4::Context->preference("OpacResetPassword"), + OpacPasswordChange => C4::Context->preference("OpacPasswordChange"), ); - + $template->param( OpacPublic => C4::Context->preference("OpacPublic")); $template->param( loginprompt => 1 ) unless $info{'nopermission'}; diff --git a/C4/Members.pm b/C4/Members.pm index 7ac69df..ccfec34 100644 --- a/C4/Members.pm +++ b/C4/Members.pm @@ -41,6 +41,12 @@ use DateTime::Format::DateParse; use Koha::DateUtils; use Text::Unaccent qw( unac_string ); +## Password recovery +use CGI; +use Encode qw(encode decode); +use MIME::QuotedPrint qw(encode_qp); +use Mail::Sendmail; + our ($VERSION,@ISA,@EXPORT,@EXPORT_OK,$debug); BEGIN { @@ -109,7 +115,12 @@ BEGIN { push @EXPORT, qw( &ModMember &changepassword - &ModPrivacy + &ModPrivacy + ); + + # Password recovery + push @EXPORT, qw( + &SendPasswordRecoveryEmail ); #Delete data @@ -2287,6 +2298,75 @@ sub ModPrivacy { privacy => $privacy ); } + +sub SendPasswordRecoveryEmail { + my $borrowernumber = shift; + my $email = shift; + my $dbh = C4::Context->dbh; + #use constant EMAIL_ENCODE = 'iso-8859-1'; + my $emailEncode = 'iso-8859-1'; + my $username = GetMemberDetails($borrowernumber)->{userid}; + + #generate UUID + my @chars = ("A".."Z", "a".."z", "0".."9"); + my $uuid_str; + $uuid_str .= $chars[rand @chars] for 1..32; + + #insert into database + my $sth = $dbh->prepare( 'INSERT INTO borrower_password_recovery VALUES (? ,? , ADDDATE(NOW(), INTERVAL 2 DAY) )' ); + $sth->execute($borrowernumber, $uuid_str); + + my $userEmail = ( $email ) ? $email : GetFirstValidEmailAddress($borrowernumber); + #define to/from emails + my $kohaEmail = C4::Context->preference( 'KohaAdminEmailAddress' ); + + #create link + my $uuidLink = "http://" . C4::Context->preference( 'OPACBaseURL' ) . "/cgi-bin/koha/opac-password-recovery.pl?uniqueKey=$uuid_str"; + #warn $uuidLink; + + #build email content + my $query = new CGI; + my ( $template2, $borrower_number, $cookie ) = C4::Auth::get_template_and_user( + { + template_name => "opac-send-password-recovery.tmpl", + query => $query, + type => "opac", + authnotrequired => 1, + } + ); + $template2->param( + uuidLink => $uuidLink, + username => $username, + ); + + # Getting template result and + my $template_res = $template2->output(); + + #Mail attributes + my %mail = ( + To => $userEmail, + From => $kohaEmail, + 'Content-Type' => 'text/html; charset="' . $emailEncode . '"', + ); + + #getting mail properties + if ( $template_res =~ /(.*)/s ) { $mail{'subject'} = decode( 'utf-8', $1 ); } + if ( $template_res =~ /\n(.*)\n/s ) { $mail{'body'} = decode( 'utf-8', $1 ); } + #send mail + if ( sendmail %mail ) + { + # if it works.... + return 1; + } + else + { + # if it doesnt work.... + warn "Error sending mail: $Mail::Sendmail::error \n"; + return 0; + } +} + + =head2 AddMessage AddMessage( $borrowernumber, $message_type, $message, $branchcode ); diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref index a75c7ef..5687edc 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref @@ -551,7 +551,14 @@ OPAC: track: "Track" no: "Don't track" - links that patrons click on - + - + - pref: OpacResetPassword + default: 1 + choices: + yes: "On" + no: "Off" + - ". If On, the user can reset his password on OPAC." + Shelf Browser: - - pref: OPACShelfBrowser diff --git a/koha-tmpl/opac-tmpl/prog/en/modules/opac-auth.tt b/koha-tmpl/opac-tmpl/prog/en/modules/opac-auth.tt index 7b8b8f9..e0d2974 100644 --- a/koha-tmpl/opac-tmpl/prog/en/modules/opac-auth.tt +++ b/koha-tmpl/opac-tmpl/prog/en/modules/opac-auth.tt @@ -85,6 +85,9 @@ please choose against which one you would like to authenticate:

+[% IF OpacPasswordChange && OpacResetPassword %] +

Forgot your password?

+[% END %]
Don't have a password yet?

If you don't have a password yet, stop by the circulation desk the next time you're in the library. We'll happily set one up for you.

Don't have a library card?

If you don't have a library card, stop by your local library to sign up[% IF PatronSelfRegistration && PatronSelfRegistrationDefaultCategory %] or register here[% END %].

diff --git a/koha-tmpl/opac-tmpl/prog/en/modules/opac-main.tt b/koha-tmpl/opac-tmpl/prog/en/modules/opac-main.tt index eb1e600..3f2bfa2 100644 --- a/koha-tmpl/opac-tmpl/prog/en/modules/opac-main.tt +++ b/koha-tmpl/opac-tmpl/prog/en/modules/opac-main.tt @@ -59,8 +59,12 @@ [% IF PatronSelfRegistration && PatronSelfRegistrationDefaultCategory %]
Don't have an account? Register here.
[% END %] - - + + + [% IF OpacPasswordChange && opacResetPassword %] +

Forgot your password?

+ [% END %] +
[% END %] diff --git a/koha-tmpl/opac-tmpl/prog/en/modules/opac-password-recovery.tt b/koha-tmpl/opac-tmpl/prog/en/modules/opac-password-recovery.tt new file mode 100644 index 0000000..195ed55 --- /dev/null +++ b/koha-tmpl/opac-tmpl/prog/en/modules/opac-password-recovery.tt @@ -0,0 +1,129 @@ +[% INCLUDE 'doc-head-open.inc' %] +[% IF (LibraryNameTitle) %][% LibraryNameTitle %][% ELSE %]Koha Online[% END %] Catalog › +[% INCLUDE 'doc-head-close.inc' %] + + + + + +
+
+[% INCLUDE 'masthead.inc' %] +
+
+
+
+[% IF (!OpacResetPassword) %] +
You can't reset your password.
+[% ELSIF (password_recovery) %] + [% IF (hasError) %] + + [% IF (sendmailError) %] + An error has occured while sending you the password recovery link. +
Please try again later. + [% ELSIF (errNoEmailFound) %] + No account was found with the email address "[% email %]" +
Check if you typed it correctly. + [% ELSIF (errTooManyEmailFound) %] + More than one account has been found for the email address: "[% email %]" +
Try yo use your alternative email if you have another. + [% ELSIF (errAlreadyStartRecovery) %] + The process of password recovery has already started for this account ("[% email %]") +
Check your emails; you should recieve the link to reset your password. +
If you didn't recieve it, click here to get a new password recovery link + [% END %] +

Please contact the staff if you need further assistance. +
+ [% END %] +
+
+ +
+ Password recovery form: +

To reset your password, enter your email address. +
A link to reset your password will be sent at this address.

+
    +
  1. +
+
+ +
+
+
+
+ +[% ELSIF (new_password) %] + [% IF (errLinkNotValid) %] +
+ We could not authentify you as the account owner. +
Be sure to use the link you recieved in your email. +
+ [% ELSE %] + [% IF (hasError) %] + + [% IF (errPassNotMatch) %] + The passwords entered does not match. +
Please try again. + [% ELSIF (errPassTooShort) %] + The password is too short. +
The password must contain at least [% minPassLength %] characters. + [% END %] +
+ [% END %] +
+
+ +
+ Password recovery form: +

The password must contain at least [% minPassLength %] characters.

+
    +
  1. +
  2. +
+
+ + + +
+
+
+
+ [% END %] +[% ELSIF (mail_sent) %] +

A mail has been sent to "[% email %]". +
It contains a link to create a new password. +
This link will be valid for 2 days from now.

+
Click here to return to the main page. +[% ELSIF (password_reset_done) %] +

The password has been changed for the user "[% username %]". +
You can now login using this form.

+[% END %] +
+
+
+
+
+
+ [% INCLUDE 'usermenu.inc' %] +
+
+
+[% INCLUDE 'opac-bottom.inc' %] +
diff --git a/koha-tmpl/opac-tmpl/prog/en/modules/opac-send-password-recovery.tt b/koha-tmpl/opac-tmpl/prog/en/modules/opac-send-password-recovery.tt new file mode 100644 index 0000000..19d12a4 --- /dev/null +++ b/koha-tmpl/opac-tmpl/prog/en/modules/opac-send-password-recovery.tt @@ -0,0 +1,13 @@ +[% IF ( LibraryNameTitle ) %][% LibraryNameTitle %][% ELSE %]Koha Online[% END %] - Password recovery + + + +

This email has been sent in response to your password recovery request for the account [% username %].

+

+You can now create your new password using the following link: +
[% uuidLink %] +

+

This link will be valid for 2 days from this email's reception, then you must reapply if you do not change your password.

+

Thank you.

+ + diff --git a/opac/opac-main.pl b/opac/opac-main.pl index 7c6ee36..00025c5 100755 --- a/opac/opac-main.pl +++ b/opac/opac-main.pl @@ -58,6 +58,7 @@ $template->param( koha_news_count => $koha_news_count, display_daily_quote => C4::Context->preference('QuoteOfTheDay'), daily_quote => $quote, + opacResetPassword => C4::Context->preference('OpacResetPassword'), ); # If GoogleIndicTransliteration system preference is On Set paramter to load Google's javascript in OPAC search screens diff --git a/opac/opac-password-recovery.pl b/opac/opac-password-recovery.pl new file mode 100755 index 0000000..be5e36a --- /dev/null +++ b/opac/opac-password-recovery.pl @@ -0,0 +1,224 @@ +#!/usr/bin/perl + +use strict; + +use CGI; +use Mail::Sendmail; +use Digest::MD5 qw(md5_base64); +use HTML::Entities; + +use C4::Auth; +use C4::Koha; +use C4::Members qw(changepassword GetMember GetMemberDetails SendPasswordRecoveryEmail); +use C4::Output; +use C4::Context; + +my $query = new CGI; + +my ( $template, $borrowernumber, $cookie ) = get_template_and_user( + { + template_name => "opac-password-recovery.tmpl", + query => $query, + type => "opac", + authnotrequired => 1, + debug => 1, + } +); + +my $email = $query->param('email'); +my $password = $query->param('password'); +my $repeatPassword = $query->param('repeatPassword'); +my $minPassLength = C4::Context->preference( 'minPasswordLength' ); +my $id = $query->param('id'); +my $uniqueKey = $query->param('uniqueKey'); +my $username = $query->param('username'); +my $borrower_number; + +#errors +my $hasError; + +#email form error +my $errNoEmailFound; +my $errAlreadyStartRecovery; + +#new password form error +my $errLinkNotValid; +my $errPassNotMatch; +my $errPassTooShort; + +my $dbh = C4::Context->dbh; + +$template->param( OpacResetPassword => C4::Context->preference("OpacResetPassword") ); + +if ( $query->param('sendEmail') || $query->param('resendEmail') ) +{ +#send mail + confirmation + + #try with the main email + my $borrower_number; + my $accountActivated; + my %borrower_infos = GetMember(email => $email); + if ( %borrower_infos ) + { + $borrower_number = GetMember(email => $email)->{'borrowernumber'}; + $username = GetMemberDetails( $borrower_number )->{'userid'}; + $accountActivated = ( GetMemberDetails( $borrower_number )->{'categorycode'} ne 'UNAUTHORIZ' ) ? 1 : 0; + } + else + { + #try with the secondary email + %borrower_infos = GetMember(emailpro => $email); + if ( %borrower_infos ) + { + $borrower_number = GetMember(emailpro => $email)->{'borrowernumber'}; + $username = GetMemberDetails( $borrower_number )->{'userid'}; + $accountActivated = ( GetMemberDetails( $borrower_number )->{'categorycode'} ne 'UNAUTHORIZ' ) ? 1 : 0; + } + else + { + #try when the other contact email + %borrower_infos = GetMember(B_email => $email); + if ( %borrower_infos ) + { + $borrower_number = GetMember(B_email => $email)->{'borrowernumber'}; + $username = GetMemberDetails( $borrower_number )->{'userid'}; + $accountActivated = ( GetMemberDetails( $borrower_number )->{'categorycode'} ne 'UNAUTHORIZ' ) ? 1 : 0; + } + } + } + + if ( !$email || !$username || !$borrower_number || !$accountActivated ) + { + $hasError = 1; + $errNoEmailFound = 1; + } + elsif ( !$query->param('resendEmail') ) + { + my $sth = $dbh->prepare( "SELECT borrowernumber FROM borrower_password_recovery WHERE NOW() < valid_until AND borrowernumber = ?" ); + $sth->execute($borrower_number); + if ( my $already = $sth->fetchrow ) + { + $hasError = 1; + $errAlreadyStartRecovery = 1; + } + } + + if ( $hasError ) + { + $template->param( hasError => 1, + errNoEmailFound => $errNoEmailFound, + errAlreadyStartRecovery => $errAlreadyStartRecovery, + password_recovery => 1, + email => HTML::Entities::encode($email), + ); + } + else + { + #generate uuid and send recovery email + if ( SendPasswordRecoveryEmail($borrower_number, $email) ) + { + # if it works.... + $template->param( mail_sent => 1, + email => $email + ); + } + else + { + # if it doesnt work.... + $template->param( password_recovery => 1, + sendmailError => 1 + ); + } + } +} +elsif ( $query->param('passwordReset') ) +{ +#new password form + #check if the link is still valid + my $sth = $dbh->prepare( "SELECT borrower_password_recovery.borrowernumber, userid + FROM borrower_password_recovery, borrowers + WHERE borrowers.borrowernumber = borrower_password_recovery.borrowernumber + AND NOW() < valid_until + AND uuid = ?" ); + $sth->execute( $uniqueKey ); + ( $borrower_number , $username) = $sth->fetchrow; + + #validate password length & match + if ( ( $borrower_number ) && ( $password eq $repeatPassword ) && ( length( $password ) >= $minPassLength ) ) + { + #apply changes + changepassword( $username, $borrower_number, md5_base64($password) ); + #this line needed to fix a log bug in "Log.pm:75" + # + # if ( !$usernumber ) { $usernumber=0; } + # + + #remove entry + my $sth = $dbh->prepare( "DELETE FROM borrower_password_recovery + WHERE uuid = ? + ORDER BY valid_until DESC LIMIT 1" ); + $sth->execute( $uniqueKey); + + $template->param( password_reset_done => 1, + username => $username + ); + } + else + { + #errors + if ( !$borrower_number ) + { + #parameters not valid + $errLinkNotValid = 1; + } + elsif ( $password ne $repeatPassword ) + { + #passwords does not match + $errPassNotMatch = 1; + } + elsif ( length( $password ) < $minPassLength ) + { + #password too short + $errPassTooShort = 1; + } + $template->param( new_password => 1, + minPassLength => $minPassLength, + email => $email, + uniqueKey => $uniqueKey, + errLinkNotValid => $errLinkNotValid, + errPassNotMatch => $errPassNotMatch, + errPassTooShort => $errPassTooShort, + hasError => 1 ); + } +} +elsif ( $uniqueKey ) +{ +#reset password form + #check if the link is valid + my $sth = $dbh->prepare( "SELECT borrower_password_recovery.borrowernumber, userid + FROM borrower_password_recovery, borrowers + WHERE borrowers.borrowernumber = borrower_password_recovery.borrowernumber + AND NOW() < valid_until + AND uuid = ?" ); + $sth->execute( $uniqueKey ); + ( $borrower_number, $username ) = $sth->fetchrow; + if( !$borrower_number ) + { + $errLinkNotValid = 1; + } + $template->param( new_password => 1, + minPassLength => $minPassLength, + email => $email, + uniqueKey => $uniqueKey, + username => $username, + errLinkNotValid => $errLinkNotValid + ); +} +else +{ + #password recovery form (to send email) + $template->param( password_recovery => 1 ); +} + +output_html_with_http_headers $query, $cookie, $template->output; + -- 1.7.10.4