site/templates/user/base_bookseminar.tmpl
site/templates/user/base_cancelbooking.tmpl
site/templates/user/base_editbooking.tmpl
+site/templates/user/base_lost_prompt.tmpl
site/templates/user/base_orderdetail.tmpl
site/templates/user/base_orderdetaila.tmpl
site/templates/user/base_redirect.tmpl
password_type varchar(20) not null default 'plain',
+ -- for password recovery
+ -- number of attempts today
+ lost_today integer not null default 0,
+ -- what today refers to
+ lost_date date null,
+ -- the hash the customer needs to supply to change their password
+ lost_id varchar(32) null,
+
primary key (id),
unique (userId),
index (affiliate_name)
user/logon.tmpl = user,user/logon_base.tmpl
user/lostemailsent.tmpl = user,user/lostemailsent_base.tmpl
user/lostpassword.tmpl = user,user/lostpassword_base.tmpl
+user/lost_prompt.tmpl = user,user/base_lost_prompt.tmpl
user/nopassword.tmpl = user,user/nopassword_base.tmpl
user/options.tmpl = user,user/options_base.tmpl
user/options_saved.tmpl = user,user/options_saved_base.tmpl
use base 'BSE::UI::UserCommon';
use Carp qw(confess);
-our $VERSION = "1.014";
+our $VERSION = "1.015";
use constant MAX_UNACKED_CONF_MSGS => 3;
use constant MIN_UNACKED_CONF_GAP => 2 * 24 * 60 * 60;
download_file=>'download_file',
show_lost_password => 'show_lost_password',
lost_password => 'lost_password',
+ lost => 1,
+ lost_save => 1,
subinfo => 'subinfo',
blacklist => 'blacklist',
confirm => 'confirm',
$message = escape_html($message);
$errors = {};
}
+ else {
+ $message = $req->message();
+ }
my %acts;
%acts =
(
$errors{old_password} = $msgs->(optsbadold=>"You need to enter your old password to change your password")
}
else {
- my $min_pass_length = $cfg->entry('basic', 'minpassword') || 4;
my $error;
- if (length $newpass < $min_pass_length) {
- $errors{password} = $msgs->(optspasslen=>
- "The password must be at least $min_pass_length characters",
- $min_pass_length);
+ if (!SiteUser->check_password_rules($newpass, \$error)) {
+ my ($code, @more) = @$error;
+ $errors{password} = $req->catmsg("msg:bse/user/$code", \@more)
}
elsif (!defined $confirm || length $confirm == 0) {
$errors{confirm_password} = $msgs->(optsconfpass=>"Please enter a confirmation password");
keys %errors
and return $self->req_show_lost_password($req, \%errors);
- my $custom = custom_class($cfg);
- my $email_user = $user;
- if ($custom->can('send_user_email_to')) {
- eval {
- $email_user = $custom->send_user_email_to($user, $cfg);
- };
- }
- require BSE::ComposeMail;
- my $mail = BSE::ComposeMail->new(cfg => $cfg);
-
- my %mailacts;
- %mailacts =
- (
- BSE::Util::Tags->static(\%mailacts, $cfg),
- user => sub { $user->{$_[0]} },
- host => sub { $ENV{REMOTE_ADDR} },
- site => sub { $cfg->entryErr('site', 'url') },
- emailuser => [ \&tag_hash_plain, $email_user ],
- );
- my $from = $cfg->entry('confirmations', 'from') ||
- $cfg->entry('basic', 'emailfrom') || $SHOP_FROM;
- my $nopassword = $cfg->entryBool('site users', 'nopassword', 0);
- my $subject = $cfg->entry('basic', 'lostpasswordsubject')
- || ($nopassword ? "Your options" : "Your password");
- $mail->send(template => 'user/lostpwdemail',
- acts => \%mailacts,
- from=>$from,
- to=>$email_user->{email},
- subject=>$subject)
- or return $self->req_show_lost_password($req,
- $msgs->(lostmailerror=>
- "Email error:".$mail->errstr,
- $mail->errstr));
+ my $error;
+ my $email_user = $user->lost_password(\$error)
+ or return $self->req_show_lost_password
+ ($req, $msgs->(lostmailerror=> "Email error: .$error", $error));
$message = $message ? escape_html($message) : $req->message;
my %acts;
%acts =
or return $self->error($req, $msg);
}
+sub req_lost {
+ my ($self, $req, $errors) = @_;
+
+ my ($id) = $self->rest;
+ $id ||= $req->cgi->param("id");
+ $id
+ or return $self->req_show_logon($req, $req->catmsg("msg:bse/user/nolostid"));
+
+ my $error;
+ my $user = SiteUsers->lost_password_next($id, \$error)
+ or return $self->req_show_logon($req, { _ => "msg:bse/user/lost/$error" });
+
+ my $message = $req->message($errors);
+
+ my %acts =
+ (
+ $req->dyn_user_tags,
+ lostid => $id,
+ error_img => [ \&tag_error_img, $req->cfg, $errors ],
+ message => $message,
+ );
+
+ return $req->response("user/lost_prompt", \%acts);
+}
+
+my %lost_fields =
+ (
+ password =>
+ {
+ description => "New Password",
+ required => 1,
+ },
+ confirm =>
+ {
+ description => "Confirm Password",
+ rules => "confirm",
+ required => 1,
+ },
+ );
+
+sub req_lost_save {
+ my ($self, $req) = @_;
+
+ my ($id) = $self->rest;
+ $id ||= $req->cgi->param("id");
+ $id
+ or return $self->req_show_logon($req, $req->catmsg("msg:bse/user/nolostid"));
+
+ my %errors;
+ $req->validate(fields => \%lost_fields,
+ errors => \%errors);
+ my $password = $req->cgi->param("password");
+ unless ($errors{password}) {
+ my $error;
+ unless (SiteUser->check_password_rules($password, \$error)) {
+ my ($errorid, @more) = @$error;
+ $errors{password} = $req->catmsg("msg:bse/user/$errorid", \@more)
+ }
+ }
+
+ keys %errors
+ and return $self->req_lost($req, \%errors);
+
+ my $error;
+
+ my $user = SiteUsers->lost_password_save($id, $password, \$error)
+ or return $self->req_show_logon($req, "msg:bse/user/lost/$error");
+
+ $req->flash("msg:bse/user/lostsaved");
+
+ return $req->get_refresh($req->cfg->user_url("user", "show_logon"));
+}
+
+
+
1;
use Carp qw(confess);
use BSE::Util::SQL qw/now_datetime now_sqldate sql_normal_date sql_add_date_days/;
-our $VERSION = "1.005";
+our $VERSION = "1.006";
use constant MAX_UNACKED_CONF_MSGS => 3;
use constant MIN_UNACKED_CONF_GAP => 2 * 24 * 60 * 60;
affiliate_name delivMobile billMobile
delivStreet2 billStreet2
billOrganization
- customInt1 customInt2 password_type/;
+ customInt1 customInt2 password_type
+ lost_today lost_date lost_id/;
}
sub table {
billOrganization => "",
customInt1 => "",
customInt2 => "",
- #password_type
+ #password_type,
+ lost_today => 0,
+ lost_date => undef,
+ lost_id => undef,
);
}
}
sub changepw {
- my ($self, $password, $who) = @_;
+ my ($self, $password, $who, %log) = @_;
require BSE::Passwords;
actor => $who,
level => "info",
msg => "Change password",
+ %log,
);
1;
return BSE::Passwords->check_password_hash($self->password, $self->password_type, $password, $error);
}
+=item lost_password
+
+Call to send a lost password email.
+
+=cut
+
+sub lost_password {
+ my ($self, $error) = @_;
+
+ my $cfg = BSE::Cfg->single;
+ require BSE::CfgInfo;
+ my $custom = BSE::CfgInfo::custom_class($cfg);
+ my $email_user = $self;
+ my $to = $self;
+ if ($custom->can('send_user_email_to')) {
+ eval {
+ $email_user = $custom->send_user_email_to($self, $cfg);
+ };
+ $to = $email_user->{email};
+ }
+ else {
+ require BSE::Util::SQL;
+ my $lost_limit = $cfg->entry("lost password", "daily_limit", 3);
+ my $today = BSE::Util::SQL::now_sqldate();
+ my $lost_today = 0;
+ if ($self->lost_date
+ && $self->lost_date eq $today) {
+ $lost_today = $self->lost_today;
+ }
+ if ($lost_today+1 > $lost_limit) {
+ $$error = "Too many password recovery attempts today, please try again tomorrow";
+ return;
+ }
+ $self->set_lost_date($today);
+ $self->set_lost_today($lost_today+1);
+ $self->set_lost_id(BSE::Util::Secure::make_secret($cfg));
+ }
+
+ require BSE::ComposeMail;
+ my $mail = BSE::ComposeMail->new(cfg => $cfg);
+
+ require BSE::Util::Tags;
+ my %mailacts;
+ %mailacts =
+ (
+ BSE::Util::Tags->mail_tags(),
+ user => [ \&BSE::Util::Tags::tag_object_plain, $self ],
+ host => $ENV{REMOTE_ADDR},
+ site => $cfg->entryErr('site', 'url'),
+ emailuser => [ \&BSE::Util::Tags::tag_hash_plain, $email_user ],
+ );
+ my $from = $cfg->entry('confirmations', 'from') ||
+ $cfg->entry('basic', 'emailfrom') || $SHOP_FROM;
+ my $nopassword = $cfg->entryBool('site users', 'nopassword', 0);
+ my $subject = $cfg->entry('basic', 'lostpasswordsubject')
+ || ($nopassword ? "Your options" : "Your password");
+ unless ($mail->send
+ (
+ template => 'user/lostpwdemail',
+ acts => \%mailacts,
+ from=>$from,
+ to => $to,
+ subject=>$subject,
+ log_msg => "Sending lost password recovery email",
+ log_component => "siteusers:lost:send",
+ log_object => $self,
+ )) {
+ $$error = $mail->errstr;
+ return;
+ }
+ $self->save;
+
+ return $email_user;
+}
+
+sub check_password_rules {
+ my ($class, $password, $error) = @_;
+
+ my $cfg = BSE::Cfg->single;
+ my $min_pass_length = $cfg->entry('basic', 'minpassword') || 4;
+ if (length $password < $min_pass_length) {
+ $$error = [ "passwordlen", $min_pass_length ];
+ return;
+ }
+
+ return 1;
+}
+
1;
@ISA = qw(Squirrel::Table);
use SiteUser;
-our $VERSION = "1.001";
+our $VERSION = "1.002";
sub rowClass {
return 'SiteUser';
return $self->SUPER::make(%opts);
}
+sub _lost_user {
+ my ($self, $id, $error) = @_;
+
+ my ($user) = SiteUsers->getBy(lost_id => $id);
+ unless ($user) {
+ $$error = "unknownid";
+ return;
+ }
+
+ require BSE::Util::SQL;
+ my $lost_limit_days = BSE::Cfg->single->entry("lost password", "age_limit", 7);
+ my $check_date = BSE::Util::SQL::sql_add_date_days($user->lost_date, $lost_limit_days);
+
+ my $today = BSE::Util::SQL::now_sqldate();
+
+ if ($check_date lt $today) {
+ $$error = "expired";
+ return;
+ }
+
+ return $user;
+}
+
+sub lost_password_next {
+ my ($self, $id, $error) = @_;
+
+ my $user = $self->_lost_user($id, $error)
+ or return;
+
+ return $user;
+}
+
+sub lost_password_save {
+ my ($self, $id, $password, $error) = @_;
+
+ my $user = $self->_lost_user($id, $error)
+ or return;
+
+ $user->changepw($password, $user,
+ component => "siteusers:lost:changepw",
+ msg => "Account recovered");
+ $user->set_lost_id("");
+ $user->save;
+
+ return 1;
+}
+
1;
id: bse/user/optsrequired
description: Message displayed for a required field during registration or when saving user details
+id: bse/user/nolostid
+description: Displayed if the lost handler doesn't receive an id.
+
+id: bse/user/passwordlen
+description: Displayed when a new password is too short, $1 is the min length
+
+id: bse/user/lostsaved
+description: Displayed when a new password is saved during account recovery
+
+id: bse/user/lost/
+description: Lost password errors
+
+id: bse/user/lost/unknownid
+description: Displayed if the supplied lost password id isn't known
+
+id: bse/user/lost/expired
+description: Displayed if the supplied lost password has expired
+
id: bse/admin/
description: BSE Administration
id: bse/user/baduserpass
message: Invalid username or password
+id: bse/user/nolostid
+message: No id supplied for password recovery
+
+id: bse/user/passwordlen
+message: New password must be at least %1:s characters
+
+id: bse/user/lostsaved
+message: Your new password has been saved, please logon
+
+id: bse/user/lost/unknownid
+message: Unknown identifier for account recovery. Please try again.
+
+id: bse/user/lost/expired
+message: Your account recovery URL is too old. Please try again.
+
id: bse/admin/edit/uplabelsect
message: -- move up a level -- become a section
=back
+=head2 [lost password]
+
+=over
+
+=item *
+
+daily_limit - the number of recovery attempts permitted per day.
+Default: 3.
+
+=item *
+
+age_limit - the id included in the email is valid for this many days.
+Default: 7.
+
+=back
+
=head1 AUTHOR
Tony Cook <tony@develop-help.com>
--- /dev/null
+<:wrap base.tmpl:>
+<div align="center">
+ <table>
+ <tr>
+ <th colspan="2" align="center">
+ <p><b><font face="Verdana, Arial, Helvetica, sans-serif" size="3"><:if Cfg "site users" nopassword:><:or Cfg:>Account Recovery<:eif Cfg:></font></b></p>
+ </th>
+ </tr>
+ <form action="/cgi-bin/user.pl" method="post">
+<input type="hidden" name="id" value="<:lostid:>" />
+<:ifMessage:>
+ <tr>
+ <td colspan="2" align="center"><font face="Verdana, Arial, Helvetica, sans-serif" size="2">
+ <b><:message:></b> </font></td>
+ </tr><:or:><:eif:>
+ <tr>
+ <th>New Password:</th>
+ <td>
+ <input type="password" name="password" value="" size="40" id="password" accesskey="p" tabindex="10" />
+ </td>
+ <td><:error_img password:></td>
+ </tr>
+ <tr>
+ <th>Confirm Password:</th>
+ <td>
+ <input type="password" name="confirm" value="" size="40" id="confirm" accesskey="c" tabindex="20" />
+ </td>
+ <td><:error_img confirm:></td>
+ </tr>
+ <tr>
+ <td colspan="3" align="right">
+ <input type="submit" name="a_lost_save" value="Save Password" tabindex="30" />
+ </td>
+ </tr>
+ </form>
+ </table>
+</div>
\ No newline at end of file
<:or Cfg:>
<tr>
<td align="center"><font face="Verdana, Arial, Helvetica, sans-serif" size="2">
- Your password has been emailed to <b><:emailuser email:></b>. </font></td>
+ A link to the next step in password recovery has been sent to <b><:emailuser email:></b>. </font></td>
</tr>
<tr>
<td align="center"> <br>
<:or Cfg:>
<tr>
<td colspan="2"><font face="Verdana, Arial, Helvetica, sans-serif" size="2">
- Please enter your Logon Name. Your password will be emailed to the address
- you entered when you registered. </font></td>
+ Please enter your Logon Name. You will be sent a URL you can use to change your password. </font></td>
</tr>
<:eif Cfg:>
<:ifMessage:>
</tr>
<tr>
<td colspan="2" align="right">
- <input type="submit" name="lost_password" value="<:if Cfg "site users" nopassword:>Mail Options Link<:or Cfg:>Mail Password<:eif Cfg:>" />
+ <input type="submit" name="lost_password" value="<:if Cfg "site users" nopassword:>Mail Options Link<:or Cfg:>Next<:eif Cfg:>" />
</td>
</tr>
</form>
<:cfg site url:>/cgi-bin/user.pl?show_opts=<:user password:>&u=<:user id:>
-<:or Cfg:>We have received a request from <:host:> asking
-<:site:> to send you a reminder of your password.
+<:or Cfg:>We have received a request from <:host:> indicating that
+you've forgotten or lost your password.
-If you did not make this request, please be reassured that your password is
-only forwarded to your registered email address for security reasons.
-No-one else can access it.
-
-Your password is:
+To take the next step in recovering your account, visit:
- <:user password:>
+<:cfg site secureurl:>/cgi-bin/user.pl/lost/<:user lost_id:>
<:eif Cfg:>Any questions should be directed to: <:adminEmail:>
Column customInt1;int(11);YES;NULL;
Column customInt2;int(11);YES;NULL;
Column password_type;varchar(20);NO;plain;
+Column lost_today;int(11);NO;0;
+Column lost_date;date;YES;NULL;
+Column lost_id;varchar(32);YES;NULL;
Index PRIMARY;1;[id]
Index affiliate_name;0;[affiliate_name]
Index userId;1;[userId]