[open-ils-commits] r16269 - in trunk/Open-ILS: examples examples/apache src src/extras src/perlmods/OpenILS/Application src/perlmods/OpenILS/WWW src/sql/Pg src/sql/Pg/upgrade src/templates src/templates/password-reset web/js/dojo/openils/opac/nls web/opac/skin/default/js web/opac/skin/default/xml/common (dbs)
svn at svn.open-ils.org
svn at svn.open-ils.org
Mon Apr 19 08:47:13 EDT 2010
Author: dbs
Date: 2010-04-19 08:47:12 -0400 (Mon, 19 Apr 2010)
New Revision: 16269
Added:
trunk/Open-ILS/src/perlmods/OpenILS/WWW/PasswordReset.pm
trunk/Open-ILS/src/sql/Pg/upgrade/0237.data.password-reset.sql
trunk/Open-ILS/src/templates/password-reset/
trunk/Open-ILS/src/templates/password-reset/request-form.tt2
trunk/Open-ILS/src/templates/password-reset/reset-form.tt2
trunk/Open-ILS/src/templates/password-reset/strings.en-US
trunk/Open-ILS/web/opac/skin/default/js/password_reset.js
Modified:
trunk/Open-ILS/examples/apache/eg.conf
trunk/Open-ILS/examples/apache/eg_vhost.conf
trunk/Open-ILS/examples/apache/startup.pl
trunk/Open-ILS/examples/fm_IDL.xml
trunk/Open-ILS/examples/opensrf.xml.example
trunk/Open-ILS/src/Makefile.am
trunk/Open-ILS/src/extras/ils_events.xml
trunk/Open-ILS/src/perlmods/OpenILS/Application/Actor.pm
trunk/Open-ILS/src/sql/Pg/002.schema.config.sql
trunk/Open-ILS/src/sql/Pg/005.schema.actors.sql
trunk/Open-ILS/src/sql/Pg/950.data.seed-values.sql
trunk/Open-ILS/web/js/dojo/openils/opac/nls/opac.js
trunk/Open-ILS/web/opac/skin/default/xml/common/login.xml
Log:
Forward-port self-serve password reset implementation from rel_1_6
TODO: Add support for craftsman
TODO: Add barcode + email input option
TODO: Document how it works
Modified: trunk/Open-ILS/examples/apache/eg.conf
===================================================================
--- trunk/Open-ILS/examples/apache/eg.conf 2010-04-19 00:04:26 UTC (rev 16268)
+++ trunk/Open-ILS/examples/apache/eg.conf 2010-04-19 12:47:12 UTC (rev 16269)
@@ -19,8 +19,8 @@
PerlChildInitHandler OpenILS::WWW::Reporter::child_init
PerlChildInitHandler OpenILS::WWW::SuperCat::child_init
PerlChildInitHandler OpenILS::WWW::AddedContent::child_init
+PerlChildInitHandler OpenILS::WWW::PasswordReset::child_init
-
# ----------------------------------------------------------------------------------
# Set some defaults for our working directories
# ----------------------------------------------------------------------------------
Modified: trunk/Open-ILS/examples/apache/eg_vhost.conf
===================================================================
--- trunk/Open-ILS/examples/apache/eg_vhost.conf 2010-04-19 00:04:26 UTC (rev 16268)
+++ trunk/Open-ILS/examples/apache/eg_vhost.conf 2010-04-19 12:47:12 UTC (rev 16269)
@@ -163,7 +163,22 @@
allow from all
</LocationMatch>
+# ----------------------------------------------------------------------------------
+# Self-serve password interface
+# ----------------------------------------------------------------------------------
+<Location /opac/password>
+ SetHandler perl-script
+ PerlHandler OpenILS::WWW::PasswordReset::password_reset
+ Options +ExecCGI
+ PerlSendHeader On
+ allow from all
+ # Force clients to use HTTPS
+ RewriteEngine On
+ RewriteCond %{HTTPS} !=on [NC]
+ RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [R,L]
+</Location>
+
# ----------------------------------------------------------------------------------
# Supercat feeds
# ----------------------------------------------------------------------------------
Modified: trunk/Open-ILS/examples/apache/startup.pl
===================================================================
--- trunk/Open-ILS/examples/apache/startup.pl 2010-04-19 00:04:26 UTC (rev 16268)
+++ trunk/Open-ILS/examples/apache/startup.pl 2010-04-19 12:47:12 UTC (rev 16269)
@@ -6,6 +6,7 @@
use OpenILS::WWW::Proxy ('/openils/conf/opensrf_core.xml');
use OpenILS::WWW::Vandelay qw( /openils/conf/opensrf_core.xml );
use OpenILS::WWW::EGWeb ('/openils/conf/oils_web.xml');
+use OpenILS::WWW::PasswordReset ('/openils/conf/opensrf_core.xml');
# - Uncoment the following 2 lines to make use of the IP redirection code
# - The IP file should to contain a map with the following format:
Modified: trunk/Open-ILS/examples/fm_IDL.xml
===================================================================
--- trunk/Open-ILS/examples/fm_IDL.xml 2010-04-19 00:04:26 UTC (rev 16268)
+++ trunk/Open-ILS/examples/fm_IDL.xml 2010-04-19 12:47:12 UTC (rev 16269)
@@ -1222,6 +1222,18 @@
<link field="creator" reltype="has_a" key="id" map="" class="au"/>
</links>
</class>
+ <class id="aupr" controller="open-ils.cstore" oils_obj:fieldmapper="actor::usr_password_reset" oils_persist:tablename="actor.usr_password_reset" reporter:label="User password reset requests">
+ <fields oils_persist:primary="id" oils_persist:sequence="actor.usr_password_reset_id_seq">
+ <field reporter:label="Request ID" name="id" reporter:datatype="id"/>
+ <field reporter:label="UUID" name="uuid" reporter:datatype="text"/>
+ <field reporter:label="User" name="usr" reporter:datatype="link"/>
+ <field reporter:label="Request Time" name="request_time" reporter:datatype="timestamp"/>
+ <field reporter:label="Was Reset?" name="has_been_reset" reporter:datatype="bool"/>
+ </fields>
+ <links>
+ <link field="usr" reltype="has_a" key="id" class="au"/>
+ </links>
+ </class>
<class id="aus" controller="open-ils.cstore" oils_obj:fieldmapper="actor::user_setting" oils_persist:tablename="actor.usr_setting" reporter:label="User Setting">
<fields oils_persist:primary="id" oils_persist:sequence="actor.usr_setting_id_seq">
<field reporter:label="Setting ID" name="id" reporter:datatype="id" />
Modified: trunk/Open-ILS/examples/opensrf.xml.example
===================================================================
--- trunk/Open-ILS/examples/opensrf.xml.example 2010-04-19 00:04:26 UTC (rev 16268)
+++ trunk/Open-ILS/examples/opensrf.xml.example 2010-04-19 12:47:12 UTC (rev 16269)
@@ -19,6 +19,7 @@
<xsl>LOCALSTATEDIR/xsl</xsl>
<script>LOCALSTATEDIR</script>
<script_lib>LOCALSTATEDIR</script_lib>
+ <templates>LOCALSTATEDIR/templates</templates>
</dirs>
<!-- global data visibility settings -->
Modified: trunk/Open-ILS/src/Makefile.am
===================================================================
--- trunk/Open-ILS/src/Makefile.am 2010-04-19 00:04:26 UTC (rev 16268)
+++ trunk/Open-ILS/src/Makefile.am 2010-04-19 12:47:12 UTC (rev 16269)
@@ -170,6 +170,7 @@
$(MKDIR_P) $(TEMPLATEDIR)
cp -r @srcdir@/perlmods/* $(perldir)
cp -r @srcdir@/templates/marc $(TEMPLATEDIR)
+ cp -r @srcdir@/templates/password-reset $(TEMPLATEDIR)
sed -i 's|SYSCONFDIR|@sysconfdir@|g' '$(DESTDIR)@libdir@/perl5/OpenILS/WWW/Web.pm'
sed -i 's|SYSCONFDIR|@sysconfdir@|g' '$(DESTDIR)@libdir@/perl5/OpenILS/WWW/Method.pm'
@echo "Installing string templates to $(TEMPLATEDIR)"
Modified: trunk/Open-ILS/src/extras/ils_events.xml
===================================================================
--- trunk/Open-ILS/src/extras/ils_events.xml 2010-04-19 00:04:26 UTC (rev 16268)
+++ trunk/Open-ILS/src/extras/ils_events.xml 2010-04-19 12:47:12 UTC (rev 16269)
@@ -779,7 +779,17 @@
<event code='5000' textcode='PERM_FAILURE'>
<desc xml:lang="en-US">Permission Denied</desc>
</event>
+ <event code='7025' textcode='PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS'>
+ <desc xml:lang='en-US'>There are too many active password reset request sessions for this patron.</desc>
+ </event>
+ <event code='7026' textcode='PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST'>
+ <desc xml:lang='en-US'>The user attempted to update their password using a stale or inactive password reset request session.</desc>
+ </event>
+ <event code='7027' textcode='PATRON_PASSWORD_WAS_NOT_STRONG'>
+ <desc xml:lang='en-US'>The user attempted to set their password to a weak value.</desc>
+ </event>
+
<!-- ================================================================ -->
<!-- CIRC EVENTS -->
<event code='7000' textcode='ROUTE_ITEM'>
Modified: trunk/Open-ILS/src/perlmods/OpenILS/Application/Actor.pm
===================================================================
--- trunk/Open-ILS/src/perlmods/OpenILS/Application/Actor.pm 2010-04-19 00:04:26 UTC (rev 16268)
+++ trunk/Open-ILS/src/perlmods/OpenILS/Application/Actor.pm 2010-04-19 12:47:12 UTC (rev 16269)
@@ -36,6 +36,8 @@
use OpenILS::Utils::Penalty;
use List::Util qw/max/;
+use UUID::Tiny qw/:std/;
+
sub initialize {
OpenILS::Application::Actor::Container->initialize();
OpenILS::Application::Actor::UserGroups->initialize();
@@ -3693,6 +3695,243 @@
return undef;
}
+__PACKAGE__->register_method(
+ method => "request_password_reset",
+ api_name => "open-ils.actor.patron.password_reset.request",
+ signature => {
+ params => [
+ { desc => 'user_id_type', type => 'string' },
+ { desc => 'user_id', type => 'string' },
+ ]
+ },
+);
+sub request_password_reset {
+ my($self, $conn, $user_id_type, $user_id) = @_;
+ # Check to see if password reset requests are already being throttled:
+ # 0. Check cache to see if we're in throttle mode (avoid hitting database)
+
+ my $e = new_editor(xact => 1);
+ my $user;
+
+ # Get the user, if any, depending on the input value
+ if ($user_id_type eq 'username') {
+ $user = $e->search_actor_user({usrname => $user_id})->[0];
+ if (!$user) {
+ $e->die_event;
+ return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
+ }
+ } elsif ($user_id_type eq 'barcode') {
+ my $card = $e->search_actor_card([
+ {barcode => $user_id},
+ {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
+ if (!$card) {
+ $e->die_event;
+ return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
+ }
+ $user = $card->usr;
+ }
+
+ # If the user doesn't have an email address, we can't help them
+ if (!$user->email) {
+ $e->die_event;
+ return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
+ }
+ _reset_password_request($conn, $e, $user);
+}
+
+# Once we have the user, we can issue the password reset request
+# XXX Add a wrapper method that accepts barcode + email input
+sub _reset_password_request {
+ my ($conn, $e, $user) = @_;
+
+ # 1. Get throttle threshold and time-to-live from OU_settings
+ my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
+ my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
+
+ my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
+
+ # 2. Get time of last request and number of active requests (num_active)
+ my $active_requests = $e->json_query({
+ from => 'aupr',
+ select => {
+ aupr => [
+ {
+ column => 'uuid',
+ transform => 'COUNT'
+ },
+ {
+ column => 'request_time',
+ transform => 'MAX'
+ }
+ ]
+ },
+ where => {
+ has_been_reset => { '=' => 'f' },
+ request_time => { '>' => $threshold_time }
+ }
+ });
+
+ # Guard against no active requests
+ if ($active_requests->[0]->{'request_time'}) {
+ my $last_request = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($active_requests->[0]->{'request_time'}));
+ my $now = DateTime::Format::ISO8601->new();
+
+ # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
+ if (($active_requests->[0]->{'usr'} > $aupr_throttle) &&
+ ($last_request->add_duration('1 minute') > $now)) {
+ $cache->put_cache('open-ils.actor.password.throttle', DateTime::Format::ISO8601->new(), 60);
+ $e->die_event;
+ return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
+ }
+ }
+
+ # TODO Check to see if the user is in a password-reset-restricted group
+
+ # Otherwise, go ahead and try to get the user.
+
+ # Check the number of active requests for this user
+ $active_requests = $e->json_query({
+ from => 'aupr',
+ select => {
+ aupr => [
+ {
+ column => 'usr',
+ transform => 'COUNT'
+ }
+ ]
+ },
+ where => {
+ usr => { '=' => $user->id },
+ has_been_reset => { '=' => 'f' },
+ request_time => { '>' => $threshold_time }
+ }
+ });
+
+ $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
+
+ # if less than or equal to per-user threshold, proceed; otherwise, return event
+ my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
+ if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
+ $e->die_event;
+ return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
+ }
+
+ # Create the aupr object and insert into the database
+ my $reset_request = Fieldmapper::actor::usr_password_reset->new;
+ my $uuid = create_uuid_as_string(UUID_V4);
+ $reset_request->uuid($uuid);
+ $reset_request->usr($user->id);
+
+ my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
+ $e->commit;
+
+ # Create an event to notify user of the URL to reset their password
+
+ # Can we stuff this in the user_data param for trigger autocreate?
+ my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
+
+ my $ses = OpenSRF::AppSession->create('open-ils.trigger');
+ $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
+
+ # Trunk only
+ # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
+
+ return 1;
+}
+
+__PACKAGE__->register_method(
+ method => "commit_password_reset",
+ api_name => "open-ils.actor.patron.password_reset.commit",
+ signature => {
+ params => [
+ { desc => 'uuid', type => 'string' },
+ { desc => 'password', type => 'string' },
+ ]
+ },
+);
+sub commit_password_reset {
+ my($self, $conn, $uuid, $password) = @_;
+
+ # Check to see if password reset requests are already being throttled:
+ # 0. Check cache to see if we're in throttle mode (avoid hitting database)
+ $cache ||= OpenSRF::Utils::Cache->new("global", 0);
+ my $throttle = $cache->get_cache('open-ils.actor.password.throttle') || undef;
+ if ($throttle) {
+ return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
+ }
+
+ my $e = new_editor(xact => 1);
+
+ my $aupr = $e->search_actor_usr_password_reset({
+ uuid => $uuid,
+ has_been_reset => 0
+ });
+
+ if (!$aupr->[0]) {
+ $e->die_event;
+ return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
+ }
+ my $user_id = $aupr->[0]->usr;
+ my $user = $e->retrieve_actor_user($user_id);
+
+ # Ensure we're still within the TTL for the request
+ my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
+ my $threshold = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time))->add(seconds => $aupr_ttl);
+ if ($threshold > DateTime->now(time_zone => 'local')) {
+ $e->die_event;
+ return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
+ }
+
+ # Check complexity of password against OU-defined regex
+ my $pw_regex = $U->ou_ancestor_setting_value($user->home_ou, 'global.password_regex');
+
+ my $is_strong = 0;
+ if ($pw_regex) {
+ # Calling JSON2perl on the $pw_regex causes failure, even before the fancy Unicode regex
+ # ($pw_regex = OpenSRF::Utils::JSON->JSON2perl($pw_regex)) =~ s/\\u([0-9a-fA-F]{4})/\\x{$1}/gs;
+ $is_strong = check_password_strength_custom($password, $pw_regex);
+ } else {
+ $is_strong = check_password_strength_default($password);
+ }
+
+ if (!$is_strong) {
+ $e->die_event;
+ return OpenILS::Event->new('PATRON_PASSWORD_WAS_NOT_STRONG');
+ }
+
+ # All is well; update the password
+ $user->passwd($password);
+ $e->update_actor_user($user);
+
+ # And flag that this password reset request has been honoured
+ $aupr->[0]->has_been_reset('t');
+ $e->update_actor_usr_password_reset($aupr->[0]);
+ $e->commit;
+
+ return 1;
+}
+
+sub check_password_strength_default {
+ my $password = shift;
+ # Use the default set of checks
+ if ( (length($password) < 7) or
+ ($password !~ m/.*\d+.*/) or
+ ($password !~ m/.*[A-Za-z]+.*/)
+ ) {
+ return 0;
+ }
+ return 1;
+}
+
+sub check_password_strength_custom {
+ my ($password, $pw_regex) = @_;
+
+ $pw_regex = qr/$pw_regex/;
+ if ($password !~ /$pw_regex/) {
+ return 0;
+ }
+ return 1;
+}
+
1;
-
Added: trunk/Open-ILS/src/perlmods/OpenILS/WWW/PasswordReset.pm
===================================================================
--- trunk/Open-ILS/src/perlmods/OpenILS/WWW/PasswordReset.pm (rev 0)
+++ trunk/Open-ILS/src/perlmods/OpenILS/WWW/PasswordReset.pm 2010-04-19 12:47:12 UTC (rev 16269)
@@ -0,0 +1,418 @@
+package OpenILS::WWW::PasswordReset;
+
+# Copyright (C) 2010 Laurentian University
+# Dan Scott <dscott at laurentian.ca>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+use strict; use warnings;
+
+use Apache2::Log;
+use Apache2::Const -compile => qw(OK REDIRECT DECLINED NOT_FOUND :log);
+use APR::Const -compile => qw(:error SUCCESS);
+use Apache2::RequestRec ();
+use Apache2::RequestIO ();
+use Apache2::RequestUtil;
+use CGI;
+use Template;
+
+use OpenSRF::EX qw(:try);
+use OpenSRF::Utils qw/:datetime/;
+use OpenSRF::Utils::Cache;
+use OpenSRF::System;
+use OpenSRF::AppSession;
+
+use OpenILS::Utils::Fieldmapper;
+use OpenSRF::Utils::Logger qw/$logger/;
+use OpenILS::Application::AppUtils;
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+
+my $log = 'OpenSRF::Utils::Logger';
+my $U = 'OpenILS::Application::AppUtils';
+
+my ($bootstrap, $actor, $templates);
+my $i18n = {};
+
+sub child_init {
+ OpenSRF::System->bootstrap_client( config_file => $bootstrap );
+
+ my $conf = OpenSRF::Utils::SettingsClient->new();
+ my $idl = $conf->config_value("IDL");
+ Fieldmapper->import(IDL => $idl);
+ $templates = $conf->config_value("dirs", "templates");
+ $actor = OpenSRF::AppSession->create('open-ils.actor');
+ load_i18n();
+}
+
+sub password_reset {
+ my $apache = shift;
+ return Apache2::Const::DECLINED if (-e $apache->filename);
+
+ $apache->content_type('text/html');
+
+ my $cgi = new CGI;
+ my $ctx = {};
+
+ $ctx->{'uri'} = $apache->uri;
+
+ # Get our locale from the URL
+ (my $locale = $apache->path_info) =~ s{^.*?/([a-z]{2}-[A-Z]{2})/.*?$}{$1};
+ if (!$locale) {
+ $locale = 'en-US';
+ }
+
+ # If locale exists, use it; otherwise fall back to en-US
+ if (exists $i18n->{$locale}) {
+ $ctx->{'i18n'} = $i18n->{$locale};
+ } else {
+ $ctx->{'i18n'} = $i18n->{'en-US'};
+ }
+
+ my $tt = Template->new({
+ INCLUDE_PATH => $templates
+ }) || die "$Template::ERROR\n";
+
+ # Get our UUID: if no UUID, then display barcode / username / email prompt
+ (my $uuid = $apache->path_info) =~ s{^/$locale/([^/]*?)$}{$1};
+ $logger->info("Password reset: UUID = $uuid");
+
+ if (!$uuid) {
+ request_password_reset($apache, $cgi, $tt, $ctx);
+ } else {
+ reset_password($apache, $cgi, $tt, $ctx, $uuid);
+ }
+}
+
+sub reset_password {
+ my ($apache, $cgi, $tt, $ctx, $uuid) = @_;
+
+ my $password_1 = $cgi->param('pwd1');
+ my $password_2 = $cgi->param('pwd2');
+
+ $ctx->{'title'} = $ctx->{'i18n'}{'TITLE'};
+ $ctx->{'password_prompt'} = $ctx->{'i18n'}{'PASSWORD_PROMPT'};
+ $ctx->{'password_prompt2'} = $ctx->{'i18n'}{'PASSWORD_PROMPT2'};
+
+ # In case non-matching passwords slip through our funky Web interface
+ if ($password_1 and $password_2 and ($password_1 ne $password_2)) {
+ $apache->status(Apache2::Const::DECLINED);
+ $ctx->{'status'} = {
+ style => 'error',
+ msg => $ctx->{'i18n'}{'NO_MATCH'}
+ };
+ $tt->process('password-reset/reset-form.tt2', $ctx)
+ || die $tt->error();
+ return Apache2::Const::OK;
+ }
+
+ if ($password_1 and $password_2 and ($password_1 eq $password_2)) {
+ my $response = $actor->request('open-ils.actor.patron.password_reset.commit', $uuid, $password_1)->gather();
+ if (ref($response) && $response->{'textcode'}) {
+ $apache->status(Apache2::Const::DECLINED);
+
+ if ($response->{'textcode'} eq 'PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST') {
+ $ctx->{'status'} = {
+ style => 'error',
+ msg => $ctx->{'i18n'}{'NOT_ACTIVE'}
+
+ };
+ }
+ if ($response->{'textcode'} eq 'PATRON_PASSWORD_WAS_NOT_STRONG') {
+ $ctx->{'status'} = {
+ style => 'error',
+ msg => $ctx->{'i18n'}{'NOT_STRONG'}
+
+ };
+ }
+ $tt->process('password-reset/reset-form.tt2', $ctx)
+ || die $tt->error();
+ return Apache2::Const::OK;
+ }
+ $ctx->{'status'} = {
+ style => 'success',
+ msg => $ctx->{'i18n'}{'SUCCESS'}
+ };
+ }
+
+ # Either the password change was successful, or this is their first time through
+ $tt->process('password-reset/reset-form.tt2', $ctx)
+ || die $tt->error();
+
+ return Apache2::Const::OK;
+}
+
+# Load our localized strings - lame, need to convert to Locale::Maketext
+sub load_i18n {
+ foreach my $string_bundle (glob("$templates/password-reset/strings.*")) {
+ open(I18NFH, '<', $string_bundle);
+ (my $locale = $string_bundle) =~ s/^.*\.([a-z]{2}-[A-Z]{2})$/$1/;
+ $logger->debug("Loaded locale [$locale] from file: [$string_bundle]");
+ while(<I18NFH>) {
+ my ($string_id, $string) = ($_ =~ m/^(.+?)=(.*?)$/);
+ $i18n->{$locale}{$string_id} = $string;
+ }
+ close(I18NFH);
+ }
+}
+
+sub request_password_reset {
+ my ($apache, $cgi, $tt, $ctx) = @_;
+
+ my $barcode = $cgi->param('barcode');
+ my $username = $cgi->param('username');
+ my $email = $cgi->param('email');
+
+ if (!($barcode or $username or $email)) {
+ $apache->status(Apache2::Const::OK);
+ $ctx->{'status'} = {
+ style => 'plain',
+ msg => $ctx->{'i18n'}{'IDENTIFY_YOURSELF'}
+ };
+ $tt->process('password-reset/request-form.tt2', $ctx)
+ || die $tt->error();
+ return Apache2::Const::OK;
+ } elsif ($barcode) {
+ my $response = $actor->request('open-ils.actor.patron.password_reset.request', 'barcode', $barcode)->gather();
+ $apache->status(Apache2::Const::OK);
+ $ctx->{'status'} = {
+ style => 'plain',
+ msg => $ctx->{'i18n'}{'REQUEST_SUCCESS'}
+ };
+ # Hide form
+ $tt->process('password-reset/request-form.tt2', $ctx)
+ || die $tt->error();
+ return Apache2::Const::OK;
+ } elsif ($username) {
+ my $response = $actor->request('open-ils.actor.patron.password_reset.request', 'username', $username)->gather();
+ $apache->status(Apache2::Const::OK);
+ $ctx->{'status'} = {
+ style => 'plain',
+ msg => $ctx->{'i18n'}{'REQUEST_SUCCESS'}
+ };
+ # Hide form
+ $tt->process('password-reset/request-form.tt2', $ctx)
+ || die $tt->error();
+ return Apache2::Const::OK;
+ }
+}
+
+1;
+
+# vim: et:ts=4:sw=4
+package OpenILS::WWW::PasswordReset;
+
+# Copyright (C) 2010 Laurentian University
+# Dan Scott <dscott at laurentian.ca>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+use strict; use warnings;
+
+use Apache2::Log;
+use Apache2::Const -compile => qw(OK REDIRECT DECLINED NOT_FOUND :log);
+use APR::Const -compile => qw(:error SUCCESS);
+use Apache2::RequestRec ();
+use Apache2::RequestIO ();
+use Apache2::RequestUtil;
+use CGI;
+use Template;
+
+use OpenSRF::EX qw(:try);
+use OpenSRF::Utils qw/:datetime/;
+use OpenSRF::Utils::Cache;
+use OpenSRF::System;
+use OpenSRF::AppSession;
+
+use OpenILS::Utils::Fieldmapper;
+use OpenSRF::Utils::Logger qw/$logger/;
+use OpenILS::Application::AppUtils;
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+
+my $log = 'OpenSRF::Utils::Logger';
+my $U = 'OpenILS::Application::AppUtils';
+
+my ($bootstrap, $actor, $templates);
+my $i18n = {};
+
+sub child_init {
+ OpenSRF::System->bootstrap_client( config_file => $bootstrap );
+
+ my $conf = OpenSRF::Utils::SettingsClient->new();
+ my $idl = $conf->config_value("IDL");
+ Fieldmapper->import(IDL => $idl);
+ $templates = $conf->config_value("dirs", "templates");
+ $actor = OpenSRF::AppSession->create('open-ils.actor');
+ load_i18n();
+}
+
+sub password_reset {
+ my $apache = shift;
+ return Apache2::Const::DECLINED if (-e $apache->filename);
+
+ $apache->content_type('text/html');
+
+ my $cgi = new CGI;
+ my $ctx = {};
+
+ $ctx->{'uri'} = $apache->uri;
+
+ # Get our locale from the URL
+ (my $locale = $apache->path_info) =~ s{^.*?/([a-z]{2}-[A-Z]{2})/.*?$}{$1};
+ if (!$locale) {
+ $locale = 'en-US';
+ }
+
+ # If locale exists, use it; otherwise fall back to en-US
+ if (exists $i18n->{$locale}) {
+ $ctx->{'i18n'} = $i18n->{$locale};
+ } else {
+ $ctx->{'i18n'} = $i18n->{'en-US'};
+ }
+
+ my $tt = Template->new({
+ INCLUDE_PATH => $templates
+ }) || die "$Template::ERROR\n";
+
+ # Get our UUID: if no UUID, then display barcode / username / email prompt
+ (my $uuid = $apache->path_info) =~ s{^/$locale/([^/]*?)$}{$1};
+ $logger->info("Password reset: UUID = $uuid");
+
+ if (!$uuid) {
+ request_password_reset($apache, $cgi, $tt, $ctx);
+ } else {
+ reset_password($apache, $cgi, $tt, $ctx, $uuid);
+ }
+}
+
+sub reset_password {
+ my ($apache, $cgi, $tt, $ctx, $uuid) = @_;
+
+ my $password_1 = $cgi->param('pwd1');
+ my $password_2 = $cgi->param('pwd2');
+
+ $ctx->{'title'} = $ctx->{'i18n'}{'TITLE'};
+ $ctx->{'password_prompt'} = $ctx->{'i18n'}{'PASSWORD_PROMPT'};
+ $ctx->{'password_prompt2'} = $ctx->{'i18n'}{'PASSWORD_PROMPT2'};
+
+ # In case non-matching passwords slip through our funky Web interface
+ if ($password_1 and $password_2 and ($password_1 ne $password_2)) {
+ $apache->status(Apache2::Const::DECLINED);
+ $ctx->{'status'} = {
+ style => 'error',
+ msg => $ctx->{'i18n'}{'NO_MATCH'}
+ };
+ $tt->process('password-reset/reset-form.tt2', $ctx)
+ || die $tt->error();
+ return Apache2::Const::OK;
+ }
+
+ if ($password_1 and $password_2 and ($password_1 eq $password_2)) {
+ my $response = $actor->request('open-ils.actor.patron.password_reset.commit', $uuid, $password_1)->gather();
+ if (ref($response) &&
+ $response->{'textcode'} &&
+ $response->{'textcode'} eq 'PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST') {
+ $apache->status(Apache2::Const::DECLINED);
+ $ctx->{'status'} = {
+ style => 'error',
+ msg => $ctx->{'i18n'}{'NOT_ACTIVE'}
+
+ };
+ $tt->process('password-reset/reset-form.tt2', $ctx)
+ || die $tt->error();
+ return Apache2::Const::OK;
+ }
+ $ctx->{'status'} = {
+ style => 'success',
+ msg => $ctx->{'i18n'}{'SUCCESS'}
+ };
+ }
+
+ # Either the password change was successful, or this is their first time through
+ $tt->process('password-reset/reset-form.tt2', $ctx)
+ || die $tt->error();
+
+ return Apache2::Const::OK;
+}
+
+# Load our localized strings - lame, need to convert to Locale::Maketext
+sub load_i18n {
+ foreach my $string_bundle (glob("$templates/password-reset/strings.*")) {
+ open(I18NFH, '<', $string_bundle);
+ (my $locale = $string_bundle) =~ s/^.*\.([a-z]{2}-[A-Z]{2})$/$1/;
+ $logger->debug("Loaded locale [$locale] from file: [$string_bundle]");
+ while(<I18NFH>) {
+ my ($string_id, $string) = ($_ =~ m/^(.+?)=(.*?)$/);
+ $i18n->{$locale}{$string_id} = $string;
+ }
+ close(I18NFH);
+ }
+}
+
+sub request_password_reset {
+ my ($apache, $cgi, $tt, $ctx) = @_;
+
+ my $barcode = $cgi->param('barcode');
+ my $username = $cgi->param('username');
+ my $email = $cgi->param('email');
+
+ if (!($barcode or $username or $email)) {
+ $apache->status(Apache2::Const::OK);
+ $ctx->{'status'} = {
+ style => 'plain',
+ msg => $ctx->{'i18n'}{'IDENTIFY_YOURSELF'}
+ };
+ $tt->process('password-reset/request-form.tt2', $ctx)
+ || die $tt->error();
+ return Apache2::Const::OK;
+ } elsif ($barcode) {
+ my $response = $actor->request('open-ils.actor.patron.password_reset.request', 'barcode', $barcode)->gather();
+ $apache->status(Apache2::Const::OK);
+ $ctx->{'status'} = {
+ style => 'plain',
+ msg => $ctx->{'i18n'}{'REQUEST_SUCCESS'}
+ };
+ # Hide form
+ $tt->process('password-reset/request-form.tt2', $ctx)
+ || die $tt->error();
+ return Apache2::Const::OK;
+ } elsif ($username) {
+ my $response = $actor->request('open-ils.actor.patron.password_reset.request', 'username', $username)->gather();
+ $apache->status(Apache2::Const::OK);
+ $ctx->{'status'} = {
+ style => 'plain',
+ msg => $ctx->{'i18n'}{'REQUEST_SUCCESS'}
+ };
+ # Hide form
+ $tt->process('password-reset/request-form.tt2', $ctx)
+ || die $tt->error();
+ return Apache2::Const::OK;
+ }
+}
+
+1;
+
+# vim: et:ts=4:sw=4
Modified: trunk/Open-ILS/src/sql/Pg/002.schema.config.sql
===================================================================
--- trunk/Open-ILS/src/sql/Pg/002.schema.config.sql 2010-04-19 00:04:26 UTC (rev 16268)
+++ trunk/Open-ILS/src/sql/Pg/002.schema.config.sql 2010-04-19 12:47:12 UTC (rev 16269)
@@ -60,7 +60,7 @@
install_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
-INSERT INTO config.upgrade_log (version) VALUES ('0236'); -- berick
+INSERT INTO config.upgrade_log (version) VALUES ('0237'); -- dbs
CREATE TABLE config.bib_source (
id SERIAL PRIMARY KEY,
Modified: trunk/Open-ILS/src/sql/Pg/005.schema.actors.sql
===================================================================
--- trunk/Open-ILS/src/sql/Pg/005.schema.actors.sql 2010-04-19 00:04:26 UTC (rev 16268)
+++ trunk/Open-ILS/src/sql/Pg/005.schema.actors.sql 2010-04-19 12:47:12 UTC (rev 16269)
@@ -520,6 +520,37 @@
CREATE INDEX actor_usr_addr_state_idx ON actor.usr_address (lower(state));
CREATE INDEX actor_usr_addr_post_code_idx ON actor.usr_address (lower(post_code));
+CREATE TABLE actor.usr_password_reset (
+ id SERIAL PRIMARY KEY,
+ uuid TEXT NOT NULL,
+ usr BIGINT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED,
+ request_time TIMESTAMP NOT NULL DEFAULT NOW(),
+ has_been_reset BOOL NOT NULL DEFAULT false
+);
+COMMENT ON TABLE actor.usr_password_reset IS $$
+/*
+ * Copyright (C) 2010 Laurentian University
+ * Dan Scott <dscott at laurentian.ca>
+ *
+ * Self-serve password reset requests
+ *
+ * ****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+$$;
+CREATE UNIQUE INDEX actor_usr_password_reset_uuid_idx ON actor.usr_password_reset (uuid);
+CREATE INDEX actor_usr_password_reset_usr_idx ON actor.usr_password_reset (usr);
+CREATE INDEX actor_usr_password_reset_request_time_idx ON actor.usr_password_reset (request_time);
+CREATE INDEX actor_usr_password_reset_has_been_reset_idx ON actor.usr_password_reset (has_been_reset);
CREATE TABLE actor.org_address (
id SERIAL PRIMARY KEY,
Modified: trunk/Open-ILS/src/sql/Pg/950.data.seed-values.sql
===================================================================
--- trunk/Open-ILS/src/sql/Pg/950.data.seed-values.sql 2010-04-19 00:04:26 UTC (rev 16268)
+++ trunk/Open-ILS/src/sql/Pg/950.data.seed-values.sql 2010-04-19 12:47:12 UTC (rev 16269)
@@ -1938,7 +1938,22 @@
( 'circ.block_renews_for_holds',
oils_i18n_gettext('circ.block_renews_for_holds', 'Holds: Block Renewal of Items Needed for Holds', 'coust', 'label'),
oils_i18n_gettext('circ.block_renews_for_holds', 'When an item could fulfill a hold, do not allow the current patron to renew', 'coust', 'description'),
- 'bool' )
+ 'bool' ),
+
+( 'circ.password_reset_request_per_user_limit',
+ oils_i18n_gettext('circ.password_reset_request_per_user_limit', 'Circulation: Maximum concurrently active self-serve password reset requests per user', 'coust', 'label'),
+ oils_i18n_gettext('circ.password_reset_request_per_user_limit', 'When a user has more than this number of concurrently active self-serve password reset requests for their account, prevent the user from creating any new self-serve password reset requests until the number of active requests for the user drops back below this number.', 'coust', 'description'),
+ 'string'),
+
+( 'circ.password_reset_request_time_to_live',
+ oils_i18n_gettext('circ.password_reset_request_time_to_live', 'Circulation: Self-serve password reset request time-to-live', 'coust', 'label'),
+ oils_i18n_gettext('circ.password_reset_request_time_to_live', 'Length of time (in seconds) a self-serve password reset request should remain active.', 'coust', 'description'),
+ 'string'),
+
+( 'circ.password_reset_request_throttle',
+ oils_i18n_gettext('circ.password_reset_request_throttle', 'Circulation: Maximum concurrently active self-serve password reset requests', 'coust', 'label'),
+ oils_i18n_gettext('circ.password_reset_request_throttle', 'Prevent the creation of new self-serve password reset requests until the number of active requests drops back below this number.', 'coust', 'description'),
+ 'string')
;
-- 0234.data.org-setting-ui.circ.suppress_checkin_popups.sql
@@ -3630,6 +3645,38 @@
( 19, 'cancel_reason' )
;
+INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('password.reset_request','aupr','Patron has requested a self-serve password reset');
+INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, delay, template)
+ VALUES (20, 'f', 1, 'Password reset request notification', 'password.reset_request', 'NOOP_True', 'SendEmail', '00:00:01',
+$$
+[%- USE date -%]
+[%- user = target.usr -%]
+To: [%- params.recipient_email || user.email %]
+From: [%- params.sender_email || user.home_ou.email || default_sender %]
+Subject: [% user.home_ou.name %]: library account password reset request
+
+You have received this message because you, or somebody else, requested a reset
+of your library system password. If you did not request a reset of your library
+system password, just ignore this message and your current password will
+continue to work.
+
+If you did request a reset of your library system password, please perform
+the following steps to continue the process of resetting your password:
+
+1. Open the following link in a web browser: https://[% params.hostname %]/opac/password/[% params.locale || 'en-US' %]/[% target.uuid %]
+The browser displays a password reset form.
+
+2. Enter your new password in the password reset form in the browser. You must
+enter the password twice to ensure that you do not make a mistake. If the
+passwords match, you will then be able to log in to your library system account
+with the new password.
+
+$$);
+INSERT INTO action_trigger.environment ( event_def, path) VALUES
+ ( 20, 'usr' );
+INSERT INTO action_trigger.environment ( event_def, path) VALUES
+ ( 20, 'usr.home_ou' );
+
SELECT SETVAL('action_trigger.event_definition_id_seq'::TEXT, 100);
-- Org Unit Settings for configuring org unit weights and org unit max-loops for hold targeting
Added: trunk/Open-ILS/src/sql/Pg/upgrade/0237.data.password-reset.sql
===================================================================
--- trunk/Open-ILS/src/sql/Pg/upgrade/0237.data.password-reset.sql (rev 0)
+++ trunk/Open-ILS/src/sql/Pg/upgrade/0237.data.password-reset.sql 2010-04-19 12:47:12 UTC (rev 16269)
@@ -0,0 +1,55 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0237');
+
+INSERT into config.org_unit_setting_type
+( name, label, description, datatype ) VALUES
+( 'circ.password_reset_request_per_user_limit',
+ oils_i18n_gettext('circ.password_reset_request_per_user_limit', 'Circulation: Maximum concurrently active self-serve password reset requests per user', 'coust', 'label'),
+ oils_i18n_gettext('circ.password_reset_request_per_user_limit', 'When a user has more than this number of concurrently active self-serve password reset requests for their account, prevent the user from creating any new self-serve password reset requests until the number of active requests for the user drops back below this number.', 'coust', 'description'),
+ 'string'),
+
+( 'circ.password_reset_request_time_to_live',
+ oils_i18n_gettext('circ.password_reset_request_time_to_live', 'Circulation: Self-serve password reset request time-to-live', 'coust', 'label'),
+ oils_i18n_gettext('circ.password_reset_request_time_to_live', 'Length of time (in seconds) a self-serve password reset request should remain active.', 'coust', 'description'),
+ 'string'),
+
+( 'circ.password_reset_request_throttle',
+ oils_i18n_gettext('circ.password_reset_request_throttle', 'Circulation: Maximum concurrently active self-serve password reset requests', 'coust', 'label'),
+ oils_i18n_gettext('circ.password_reset_request_throttle', 'Prevent the creation of new self-serve password reset requests until the number of active requests drops back below this number.', 'coust', 'description'),
+ 'string')
+;
+
+INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('password.reset_request','aupr','Patron has requested a self-serve password reset');
+INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, delay, template)
+ VALUES (20, 'f', 1, 'Password reset request notification', 'password.reset_request', 'NOOP_True', 'SendEmail', '00:00:01',
+$$
+[%- USE date -%]
+[%- user = target.usr -%]
+To: [%- params.recipient_email || user.email %]
+From: [%- params.sender_email || user.home_ou.email || default_sender %]
+Subject: [% user.home_ou.name %]: library account password reset request
+
+You have received this message because you, or somebody else, requested a reset
+of your library system password. If you did not request a reset of your library
+system password, just ignore this message and your current password will
+continue to work.
+
+If you did request a reset of your library system password, please perform
+the following steps to continue the process of resetting your password:
+
+1. Open the following link in a web browser: https://[% params.hostname %]/opac/password/[% params.locale || 'en-US' %]/[% target.uuid %]
+The browser displays a password reset form.
+
+2. Enter your new password in the password reset form in the browser. You must
+enter the password twice to ensure that you do not make a mistake. If the
+passwords match, you will then be able to log in to your library system account
+with the new password.
+
+$$);
+INSERT INTO action_trigger.environment ( event_def, path) VALUES
+ ( 20, 'usr' );
+INSERT INTO action_trigger.environment ( event_def, path) VALUES
+ ( 20, 'usr.home_ou' );
+
+COMMIT;
Added: trunk/Open-ILS/src/templates/password-reset/request-form.tt2
===================================================================
--- trunk/Open-ILS/src/templates/password-reset/request-form.tt2 (rev 0)
+++ trunk/Open-ILS/src/templates/password-reset/request-form.tt2 2010-04-19 12:47:12 UTC (rev 16269)
@@ -0,0 +1,49 @@
+<html>
+<head>
+ <title>[% i18n.REQUEST_TITLE %]</title>
+ <link rel="stylesheet" type="text/css" href="/js/dojo/dijit/themes/tundra/tundra.css" />
+ <style type="text/css">
+ body, html { font-family:helvetica,arial,sans-serif; }
+ </style>
+</head>
+<body class="tundra">
+ <h1>[% i18n.REQUEST_TITLE %]</h1>
+ <p class='[% status.style %]'>[% status.msg %]</p>
+ <form method="post" action="[% uri %]" id="requestPwdReset">
+ <table>
+ <tr>
+ <td><label for="barcode">[% i18n.BARCODE_PROMPT %] </label></td>
+ <td><input type="text" id="barcode" name="barcode" dojoType="dijit.form.TextBox"/></td>
+ </tr>
+ <tr>
+ <td><label for="username">[% i18n.USERNAME_PROMPT %] </label></td>
+ <td><input type="text" id="barcode" name="username" dojoType="dijit.form.TextBox"/></td>
+ </tr>
+ </table>
+ <!--<label for="email">[% i18n.EMAIL_PROMPT %] </label><input type="text" name="email"/></br>-->
+ <button name="submit" id="submitButton" type="submit" dojoType="dijit.form.Button">[% i18n.BUTTON_SUBMIT %]</button>
+ </form>
+</body>
+<script type="text/javascript" src="/js/dojo/dojo/dojo.js" djConfig="parseOnLoad: true"></script>
+<script type="text/javascript">
+ dojo.require("dijit.form.Button");
+ dojo.require("dijit.form.ValidationTextBox");
+</script>
+</html>
+<html>
+<head>
+ <title>[% i18n.REQUEST_TITLE %]</title>
+</head>
+<body>
+ <h1>[% i18n.REQUEST_TITLE %]</h1>
+<p class='[% status.style %]'>[% status.msg %]</p>
+<form method="post" action="[% uri %]">
+ <div>
+ <label for="barcode">[% i18n.BARCODE_PROMPT %] </label><input type="text" name="barcode"/></br>
+ <label for="username">[% i18n.USERNAME_PROMPT %] </label><input type="text" name="username"/></br>
+ <!--<label for="email">[% i18n.EMAIL_PROMPT %] </label><input type="text" name="email"/></br>-->
+ <input type="submit"/>
+ </div>
+</form>
+</body>
+</html>
Added: trunk/Open-ILS/src/templates/password-reset/reset-form.tt2
===================================================================
--- trunk/Open-ILS/src/templates/password-reset/reset-form.tt2 (rev 0)
+++ trunk/Open-ILS/src/templates/password-reset/reset-form.tt2 2010-04-19 12:47:12 UTC (rev 16269)
@@ -0,0 +1,30 @@
+<html>
+<head>
+ <title>[% title %]</title>
+</head>
+<body>
+ <h1>[% title %]</h1>
+<p class='[% status.style %]'>[% status.msg %]</p>
+<form method="post" action="[% uri %]">
+ <div>
+ <label for="pwd1">[% password_prompt %] </label><input type="password" name="pwd1"/></br>
+ <label for="pwd2">[% password_prompt2 %] </label><input type="password" name="pwd2"/></br>
+ </div>
+</form>
+</body>
+</html>
+<html>
+<head>
+ <title>[% title %]</title>
+</head>
+<body>
+ <h1>[% title %]</h1>
+<p class='[% status.style %]'>[% status.msg %]</p>
+<form method="post" action="[% uri %]">
+ <div>
+ <label for="pwd1">[% password_prompt %] </label><input type="password" name="pwd1"/></br>
+ <label for="pwd2">[% password_prompt2 %] </label><input type="password" name="pwd2"/></br>
+ </div>
+</form>
+</body>
+</html>
Added: trunk/Open-ILS/src/templates/password-reset/strings.en-US
===================================================================
--- trunk/Open-ILS/src/templates/password-reset/strings.en-US (rev 0)
+++ trunk/Open-ILS/src/templates/password-reset/strings.en-US 2010-04-19 12:47:12 UTC (rev 16269)
@@ -0,0 +1,28 @@
+BUTTON_SUBMIT=Submit
+REQUEST_TITLE=Library system password reset request form
+IDENTIFY_YOURSELF=Please enter your user name or barcode to identify your library account and request a password reset.
+REQUEST_SUCCESS=Your user name or barcode has been submitted for a password reset. If a matching account with an email address is found, you will soon receive an email at that address with further instructions for resetting your password.
+BARCODE_PROMPT=Barcode:
+USERNAME_PROMPT=User name:
+EMAIL_PROMPT=Email address associated with the account:
+NO_SESSION=Could not find the requested password reset session.
+NO_MATCH=Passwords did not match. Please try again
+NOT_ACTIVE=This was not an active password reset request. Your password has not been reset.
+NOT_STRONG=The password you chose was not considered complex enough to protect your account. Your password has not been reset.
+SUCCESS=Password has been reset.
+TITLE=Library system password reset
+PASSWORD_PROMPT=New password:
+PASSWORD_PROMPT2=Re-enter new password:
+REQUEST_TITLE=Library system password reset request form
+IDENTIFY_YOURSELF=Please enter your user name or barcode to identify your library account and request a password reset.
+REQUEST_SUCCESS=Your user name or barcode has been submitted for a password reset. If a matching account with an email address is found, you will soon receive an email at that address with further instructions for resetting your password.
+BARCODE_PROMPT=Barcode:
+USERNAME_PROMPT=User name:
+EMAIL_PROMPT=Email address associated with the account:
+NO_SESSION=Could not find the requested password reset session.
+NO_MATCH=Passwords did not match. Please try again
+NOT_ACTIVE=This was not an active password reset request. Your password has not been reset.
+SUCCESS=Password has been reset.
+TITLE=Library system password reset
+PASSWORD_PROMPT=New password:
+PASSWORD_PROMPT2=Re-enter new password:
Modified: trunk/Open-ILS/web/js/dojo/openils/opac/nls/opac.js
===================================================================
--- trunk/Open-ILS/web/js/dojo/openils/opac/nls/opac.js 2010-04-19 00:04:26 UTC (rev 16268)
+++ trunk/Open-ILS/web/js/dojo/openils/opac/nls/opac.js 2010-04-19 12:47:12 UTC (rev 16269)
@@ -1,4 +1,16 @@
{
+ "BARCODE_PROMPT": "Barcode: ",
+ "USERNAME_PROMPT": "User name: ",
+ "CANCEL_BUTTON_LABEL": "Cancel",
+ "SUBMIT_BUTTON_LABEL": "Submit",
+ "OK": "OK",
+ "PWD_RESET_RESPONSE_TITLE": "Password reset response",
+ "PWD_RESET_SUBMIT_SUCCESS": "Your request to begin the password reset process has been processed. If your account has a valid email address, you should soon receive an email containing further instructions for resetting your password.",
+ "PWD_RESET_SUBMIT_ERROR": "The system could not process your request for a password reset. Please try again, or contact circulation staff for assistance.",
+ "PWD_RESET_SUBMIT_STATUS": "Sending request...",
+ "PWD_RESET_FORGOT_PROMPT": "Forgot your password?",
+ "PWD_RESET_FORM_TITLE": "Request password reset",
+ "PWD_RESET_SUBMIT_PROMPT": "To begin the password reset process, enter either your barcode or user name in the form below and click 'Submit'",
"CREATE_MFHD": "Add MFHD Record",
"CREATED_MFHD_RECORD": "Created MFHD record for ${0}",
"DELETE_MFHD": "Delete Record",
Added: trunk/Open-ILS/web/opac/skin/default/js/password_reset.js
===================================================================
--- trunk/Open-ILS/web/opac/skin/default/js/password_reset.js (rev 0)
+++ trunk/Open-ILS/web/opac/skin/default/js/password_reset.js 2010-04-19 12:47:12 UTC (rev 16269)
@@ -0,0 +1,107 @@
+dojo.require('dojo.parser');
+dojo.require('dijit.Dialog');
+dojo.require('dijit.form.Button');
+dojo.require('dijit.form.TextBox');
+
+dojo.requireLocalization("openils.opac", "opac");
+opac_strings = dojo.i18n.getLocalization("openils.opac", "opac");
+
+dojo.addOnLoad(function() {
+
+ // Create the password reset dialog
+ var pwResetFormDlg = createResetDialog();
+ dojo.parser.parse();
+
+ // Connect the buttons to submit / cancel events that override
+ // the default actions associated with the buttons to do
+ // pleasing Ajax things
+ dojo.connect(dijit.byId("cancelButton"), "onClick", function(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ pwResetFormDlg.hide();
+ });
+ dojo.connect(dijit.byId("submitButton"), "onClick", function(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ var xhrArgs = {
+ form: dojo.byId("requestReset"),
+ handleAs: "text",
+ load: function(data) {
+ pwResetFormDlg.hide();
+ passwordSubmission(opac_strings.PWD_RESET_SUBMIT_SUCCESS);
+ },
+ error: function(error) {
+ pwResetFormDlg.hide();
+ passwordSubmission(opac_strings.PWD_RESET_SUBMIT_ERROR);
+ }
+ }
+ var deferred = dojo.xhrPost(xhrArgs);
+ });
+ dojo.place("<tr><td colspan='2' align='center'><a class='classic_link' id='pwResetLink' onClick='dijit.byId(\"pwResetFormDlg\").show();'</a></td></tr>", "login_tbody");
+ dojo.query("#pwResetLink").attr("innerHTML", opac_strings.PWD_RESET_FORGOT_PROMPT);
+
+});
+
+function passwordSubmission( msg ) {
+ var responseDialog = new dijit.Dialog({
+ title: opac_strings.PWD_RESET_RESPONSE_TITLE,
+ style: "width: 35em"
+ });
+ responseDialog.startup();
+ var requestStatusDiv = dojo.create("div", { style: "width: 30em" });
+ var requestStatusMsg = dojo.create("div", { innerHTML: msg }, requestStatusDiv);
+ var okButton = new dijit.form.Button({
+ id: "okButton",
+ type: "submit",
+ label: opac_strings.OK
+ }).placeAt(requestStatusDiv);
+ responseDialog.attr("content", requestStatusDiv);
+ responseDialog.show();
+ dojo.connect(dijit.byId("okButton"), "onClick", responseDialog, "hide");
+}
+
+function createResetDialog() {
+ var pwResetFormDlg = new dijit.Dialog({
+ id: "pwResetFormDlg",
+ title: opac_strings.PWD_RESET_FORM_TITLE,
+ style: "width: 35em"
+ });
+ pwResetFormDlg.startup();
+
+ // Instantiate the form
+ var pwResetFormDiv = dojo.create("form", { id: "requestReset", style: "width: 30em", method: "post", action: "/opac/password/en-US" });
+ dojo.create("p", { innerHTML: opac_strings.PWD_RESET_SUBMIT_PROMPT }, pwResetFormDiv);
+ var pwResetFormTable = dojo.create("table", null, pwResetFormDiv);
+ var pwResetFormTbody = dojo.create("tbody", null, pwResetFormTable);
+ var pwResetFormRow = dojo.create("tr", null, pwResetFormTbody);
+ var pwResetFormCell = dojo.create("td", null, pwResetFormRow);
+ var pwResetFormLabel = dojo.create("label", null, pwResetFormCell);
+ dojo.attr(pwResetFormCell, { innerHTML: opac_strings.BARCODE_PROMPT });
+ pwResetFormCell = dojo.create("td", null, pwResetFormRow);
+ var barcodeText = new dijit.form.TextBox({
+ name: "barcode"
+ }).placeAt(pwResetFormCell);
+ pwResetFormRow = dojo.create("tr", {}, pwResetFormTbody);
+ pwResetFormCell = dojo.create("td", {}, pwResetFormRow);
+ dojo.attr(pwResetFormCell, { innerHTML: opac_strings.USERNAME_PROMPT });
+ pwResetFormCell = dojo.create("td", {}, pwResetFormRow);
+ var usernameText = new dijit.form.TextBox({
+ name: "username"
+ }).placeAt(pwResetFormCell);
+ dojo.create("br", null, pwResetFormDiv);
+ var submitButton = new dijit.form.Button({
+ id: "submitButton",
+ type: "submit",
+ label: opac_strings.SUBMIT_BUTTON_LABEL
+ }).placeAt(pwResetFormDiv);
+ var cancelButton = new dijit.form.Button({
+ id: "cancelButton",
+ type: "cancel",
+ label: opac_strings.CANCEL_BUTTON_LABEL
+ }).placeAt(pwResetFormDiv);
+
+ // Set the content of the Dialog to the pwResetForm
+ pwResetFormDlg.attr("content", pwResetFormDiv);
+ return pwResetFormDlg;
+}
+
Modified: trunk/Open-ILS/web/opac/skin/default/xml/common/login.xml
===================================================================
--- trunk/Open-ILS/web/opac/skin/default/xml/common/login.xml 2010-04-19 00:04:26 UTC (rev 16268)
+++ trunk/Open-ILS/web/opac/skin/default/xml/common/login.xml 2010-04-19 12:47:12 UTC (rev 16269)
@@ -11,6 +11,7 @@
config.ids.login.cancel = "login_cancel_button";
config.ids.altcanvas.login = config.ids.login.box;
</script>
+ <script type='text/javascript' src='<!--#echo var="OILS_OPAC_JS_HOST"-->/skin/default/js/password_reset.js'></script>
<br/>
@@ -21,7 +22,7 @@
<br/>
<table id='login_table' class='data_grid' style='margin-left: 20px;' width='95%'>
- <tbody>
+ <tbody id='login_tbody'>
<tr>
<td><span class='login_text'>&login.username;</span></td>
<td>
More information about the open-ils-commits
mailing list