From: Ivan Kohler Date: Mon, 30 Dec 2013 09:43:16 +0000 (-0800) Subject: self-service access for contacts, RT#25533 X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=commitdiff_plain;h=cb6cca67db487271ce96b49289ada58691a2067d self-service access for contacts, RT#25533 --- diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm index aa21ac076..37d21ea7e 100644 --- a/FS/FS/ClientAPI/MyAccount.pm +++ b/FS/FS/ClientAPI/MyAccount.pm @@ -45,6 +45,7 @@ use FS::cust_pkg; use FS::payby; use FS::acct_rt_transaction; use FS::msg_template; +use FS::contact; $DEBUG = 1; $me = '[FS::ClientAPI::MyAccount]'; @@ -210,6 +211,7 @@ sub login { my $conf = new FS::Conf; my $svc_x = ''; + my $session = {}; if ( $p->{'domain'} eq 'svc_phone' && $conf->exists('selfservice_server-phone_login') ) { @@ -227,8 +229,19 @@ sub login { $svc_x = $svc_phone; + } elsif ( $p->{email} + && (my $contact = FS::contact->by_selfservice_email($p->{email})) + ) + { + return { error => 'Incorrect password.' } + unless $contact->authenticate_password($p->{'password'}); + + $session->{'custnum'} = $contact->custnum; + } else { + ( $p->{username}, $p->{domain} ) = split('@', $p->{email}) if $p->{email}; + my $svc_domain = qsearchs('svc_domain', { 'domain' => $p->{'domain'} } ) or return { error => 'Domain '. $p->{'domain'}. ' not found' }; @@ -251,31 +264,33 @@ sub login { } - my $session = { - 'svcnum' => $svc_x->svcnum, - }; + if ( $svc_x ) { + + $session->{'svcnum'} = $svc_x->svcnum; - my $cust_svc = $svc_x->cust_svc; - my $cust_pkg = $cust_svc->cust_pkg; - if ( $cust_pkg ) { - my $cust_main = $cust_pkg->cust_main; - $session->{'custnum'} = $cust_main->custnum; - if ( $conf->exists('pkg-balances') ) { - my @cust_pkg = grep { $_->part_pkg->freq !~ /^(0|$)/ } - $cust_main->ncancelled_pkgs; - $session->{'pkgnum'} = $cust_pkg->pkgnum - if scalar(@cust_pkg) > 1; + my $cust_svc = $svc_x->cust_svc; + my $cust_pkg = $cust_svc->cust_pkg; + if ( $cust_pkg ) { + my $cust_main = $cust_pkg->cust_main; + $session->{'custnum'} = $cust_main->custnum; + if ( $conf->exists('pkg-balances') ) { + my @cust_pkg = grep { $_->part_pkg->freq !~ /^(0|$)/ } + $cust_main->ncancelled_pkgs; + $session->{'pkgnum'} = $cust_pkg->pkgnum + if scalar(@cust_pkg) > 1; + } } - } - #my $pkg_svc = $svc_acct->cust_svc->pkg_svc; - #return { error => 'Only primary user may log in.' } - # if $conf->exists('selfservice_server-primary_only') - # && ( ! $pkg_svc || $pkg_svc->primary_svc ne 'Y' ); - my $part_pkg = $cust_pkg->part_pkg; - return { error => 'Only primary user may log in.' } - if $conf->exists('selfservice_server-primary_only') - && $cust_svc->svcpart != $part_pkg->svcpart([qw( svc_acct svc_phone )]); + #my $pkg_svc = $svc_acct->cust_svc->pkg_svc; + #return { error => 'Only primary user may log in.' } + # if $conf->exists('selfservice_server-primary_only') + # && ( ! $pkg_svc || $pkg_svc->primary_svc ne 'Y' ); + my $part_pkg = $cust_pkg->part_pkg; + return { error => 'Only primary user may log in.' } + if $conf->exists('selfservice_server-primary_only') + && $cust_svc->svcpart != $part_pkg->svcpart([qw( svc_acct svc_phone )]); + + } my $session_id; do { @@ -2834,6 +2849,54 @@ sub myaccount_passwd { } +#regular pw change in self-service should change contact pw too, otherwise its way too confusing. hell its confusing they're separate at all, but alas. need to support the "ISP provides email that's used as a contact email" case as well as we can. +# sub contact_passwd { +# my $p = shift; +# my($context, $session, $custnum) = _custoragent_session_custnum($p); +# return { 'error' => $session } if $context eq 'error'; +# +# return { 'error' => 'Not logged in as a contact.' } +# unless $session->{'contactnum'}; +# +# return { 'error' => "New passwords don't match." } +# if $p->{'new_password'} ne $p->{'new_password2'}; +# +# return { 'error' => 'Enter new password' } +# unless length($p->{'new_password'}); +# +# #my $search = { 'custnum' => $custnum }; +# #$search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent'; +# $custnum =~ /^(\d+)$/ or die "illegal custnum"; +# my $search = " AND selfservice_access IS NOT NULL ". +# " AND selfservice_access = 'Y' ". +# " AND ( disabled IS NULL OR disabled = '' )". +# " AND custnum IS NOT NULL AND custnum = $1"; +# $search .= " AND agentnum = ". $session->{'agentnum'} if $context eq 'agent'; +# +# my $contact = qsearchs( { +# 'table' => 'contact', +# 'addl_from' => 'LEFT JOIN cust_main USING ( custnum ) ', +# 'hashref' => { 'contactnum' => $session->{'contactnum'}, }, +# 'extra_sql' => $search, #important +# } ) +# or return { 'error' => "Email not found" }; #? how did we get logged in? +# # deleted since then? +# +# my $error = ''; +# +# # use these svc_acct length restrictions?? +# my $conf = new FS::Conf; +# $error = 'Password too short.' +# if length($p->{'new_password'}) < ($conf->config('passwordmin') || 6); +# $error = 'Password too long.' +# if length($p->{'new_password'}) > ($conf->config('passwordmax') || 8); +# +# $error ||= $contact->change_password($p->{'new_password'}); +# +# return { 'error' => $error, }; +# +# } + sub reset_passwd { my $p = shift; @@ -2841,22 +2904,57 @@ sub reset_passwd { my $verification = $conf->config('selfservice-password_reset_verification') or return { 'error' => 'Password resets disabled' }; - my $username = $p->{'username'}; + my $contact = ''; + my $svc_acct = ''; + my $cust_main = ''; + if ( $p->{'email'} ) { #new-style, changes contact and svc_acct + + $contact = FS::contact->by_selfservice_email($p->{'email'}); - my $svc_domain = qsearchs('svc_domain', { 'domain' => $p->{'domain'} } ) - or return { 'error' => 'Account not found' }; + $cust_main = $contact->cust_main if $contact; - my $svc_acct = qsearchs('svc_acct', { 'username' => $p->{'username'}, - 'domsvc' => $svc_domain->svcnum } - ) - or return { 'error' => 'Account not found' }; + #also look for an svc_acct, otherwise it would be super confusing - my $cust_pkg = $svc_acct->cust_svc->cust_pkg - or return { 'error' => 'Account not found' }; + my($username, $domain) = split('@', $p->{'email'}); + my $svc_domain = qsearchs('svc_domain', { 'domain' => $domain } ); + if ( $svc_domain ) { + $svc_acct = qsearchs('svc_acct', { 'username' => $p->{'username'}, + 'domsvc' => $svc_domain->svcnum } + ); + if ( $svc_acct ) { + my $cust_pkg = $svc_acct->cust_svc->cust_pkg; + $cust_main ||= $cust_pkg->cust_main if $cust_pkg; + + #precaution: don't change svc_acct password not part of the same + # customer as contact + $svc_acct = '' if ! $cust_pkg + || $cust_pkg->custnum != $cust_main->custnum; + } + + } + + return { 'error' => 'Email address not found' } + unless $contact || $svc_acct; + + } elsif ( $p->{'username'} ) { #old style, looks in svc_acct only + + my $svc_domain = qsearchs('svc_domain', { 'domain' => $p->{'domain'} } ) + or return { 'error' => 'Account not found' }; + + $svc_acct = qsearchs('svc_acct', { 'username' => $p->{'username'}, + 'domsvc' => $svc_domain->svcnum } + ) + or return { 'error' => 'Account not found' }; + + my $cust_pkg = $svc_acct->cust_svc->cust_pkg + or return { 'error' => 'Account not found' }; - my $cust_main = $cust_pkg->cust_main; + $cust_main = $cust_pkg->cust_main; + + } my %verify = ( + 'email' => sub { 1; }, 'paymask' => sub { my( $p, $cust_main ) = @_; $cust_main->payby =~ /^(CARD|DCRD|CHEK|DCHK)$/ @@ -2887,35 +2985,54 @@ sub reset_passwd { } - #okay, we're verified, now create a unique session + #okay, we're verified - my $reset_session = { - 'svcnum' => $svc_acct->svcnum, - }; + if ( $contact ) { - my $timeout = '1 hour'; #? + my $error = $contact->send_reset_email( + 'svcnum' => ($svc_acct ? $svc_acct->svcnum : ''), + ); + + if ( $error ) { + return { 'error' => $error }; #???? + } + + } elsif ( $svc_acct ) { + + #create a unique session + + my $reset_session = { + 'svcnum' => $svc_acct->svcnum, + }; + + my $timeout = '1 hour'; #? + + my $reset_session_id; + do { + $reset_session_id = sha512_hex(time(). {}. rand(). $$) + } until ( ! defined _cache->get("reset_passwd_$reset_session_id") ); + #just in case + + _cache->set( "reset_passwd_$reset_session_id", $reset_session, $timeout ); + + #email it + + my $msgnum = $conf->config('selfservice-password_reset_msgnum', + $cust_main->agentnum); + #die "selfservice-password_reset_msgnum unset" unless $msgnum; + return { 'error' => "selfservice-password_reset_msgnum unset" } + unless $msgnum; + my $msg_template = qsearchs('msg_template', { msgnum => $msgnum } ); + my $error = $msg_template->send( 'cust_main' => $cust_main, + 'object' => $svc_acct, + 'substitutions' => { + 'session_id' => $reset_session_id, + } + ); + if ( $error ) { + return { 'error' => $error }; #???? + } - my $reset_session_id; - do { - $reset_session_id = sha512_hex(time(). {}. rand(). $$) - } until ( ! defined _cache->get("reset_passwd_$reset_session_id") ); #just in case - - _cache->set( "reset_passwd_$reset_session_id", $reset_session, $timeout ); - - #email it - - my $msgnum = $conf->config('selfservice-password_reset_msgnum', $cust_main->agentnum); - #die "selfservice-password_reset_msgnum unset" unless $msgnum; - return { 'error' => "selfservice-password_reset_msgnum unset" } unless $msgnum; - my $msg_template = qsearchs('msg_template', { msgnum => $msgnum } ); - my $error = $msg_template->send( 'cust_main' => $cust_main, - 'object' => $svc_acct, - 'substitutions' => { - 'session_id' => $reset_session_id, - } - ); - if ( $error ) { - return { 'error' => $error }; #???? } return { 'error' => '' }; @@ -2931,14 +3048,38 @@ sub check_reset_passwd { my $reset_session = _cache->get('reset_passwd_'. $p->{'session_id'}) or return { 'error' => "Can't resume session" }; #better error message - my $svcnum = $reset_session->{'svcnum'}; + if ( $reset_session->{'svcnum'} ) { - my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $svcnum } ) - or return { 'error' => "Service not found" }; + my $svcnum = $reset_session->{'svcnum'}; - return { 'error' => '', - 'username' => $svc_acct->username, - }; + my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $svcnum } ) + or return { 'error' => "Service not found" }; + + return { 'error' => '', + 'session_id' => $p->{'session_id'}, + 'username' => $svc_acct->username, + }; + + } elsif ( $reset_session->{'contactnum'} ) { + + my $contactnum = $reset_session->{'contactnum'}; + + my $contact = qsearchs('contact', { 'contactnum' => $contactnum } ) + or return { 'error' => "Contact not found" }; + + my @contact_email = $contact->contact_email; + return { 'error' => 'No contact email' } unless @contact_email; + + return { 'error' => '', + 'session_id' => $p->{'session_id'}, + 'email' => $contact_email[0]->email, #the first? + }; + + } else { + + return { 'error' => 'No svcnum or contactnum in session' }; #?? + + } } @@ -2958,20 +3099,43 @@ sub process_reset_passwd { my $reset_session = _cache->get('reset_passwd_'. $p->{'session_id'}) or return { 'error' => "Can't resume session" }; #better error message - my $svcnum = $reset_session->{'svcnum'}; + if ( $reset_session->{'svcnum'} ) { - my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $svcnum } ) - or return { 'error' => "Service not found" }; + my $svcnum = $reset_session->{'svcnum'}; - $svc_acct->set_password($p->{'new_password'}); - my $error = $svc_acct->replace(); + my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $svcnum } ) + or return { 'error' => "Service not found" }; - my($label, $value) = $svc_acct->cust_svc->label; + $svc_acct->set_password($p->{'new_password'}); + my $error = $svc_acct->replace(); - return { 'error' => $error, - #'label' => $label, - #'value' => $value, - }; + return { 'error' => $error } if $error; + + #my($label, $value) = $svc_acct->cust_svc->label; + #return { 'error' => $error, + # #'label' => $label, + # #'value' => $value, + # }; + + } + + if ( $reset_session->{'contactnum'} ) { + + my $contactnum = $reset_session->{'contactnum'}; + + my $contact = qsearchs('contact', { 'contactnum' => $contactnum } ) + or return { 'error' => "Contact not found" }; + + my $error = $contact->change_password($p->{'new_password'}); + + return { 'error' => $error }; # if $error; + + } + + #password changed ,so remove session, don't want it reused + _cache->remove($p->{'session_id'}); + + return { 'error' => '' }; } diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index eb4741386..375d2d899 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -2837,7 +2837,8 @@ and customer address. Include units.', 'description' => 'If enabled, specifies the type of verification required for self-service password resets.', 'type' => 'select', 'select_hash' => [ '' => 'Password reset disabled', - 'paymask,amount,zip' => 'Verify with credit card (or bank account) last 4 digits, payment amount and zip code', + 'email' => 'Click on a link in email', + 'paymask,amount,zip' => 'Click on a link in email, and also verify with credit card (or bank account) last 4 digits, payment amount and zip code', ], }, diff --git a/FS/FS/Setup.pm b/FS/FS/Setup.pm index e27b66fc5..29130fa61 100644 --- a/FS/FS/Setup.pm +++ b/FS/FS/Setup.pm @@ -400,6 +400,9 @@ sub initial_data { #phone types 'phone_type' => [], + #message templates + 'msg_template' => [], + ; \%hash; diff --git a/FS/FS/contact.pm b/FS/FS/contact.pm index 3f73bd63c..2a73b9cf6 100644 --- a/FS/FS/contact.pm +++ b/FS/FS/contact.pm @@ -2,8 +2,11 @@ package FS::contact; use base qw( FS::Record ); use strict; +use Scalar::Util qw( blessed ); use FS::Record qw( qsearchs dbh ); # qw( qsearch qsearchs dbh ); use FS::contact_phone; +use FS::contact_email; +use FS::queue; =head1 NAME @@ -168,6 +171,14 @@ sub insert { } #} + if ( $self->selfservice_access ) { + my $error = $self->send_reset_email( queue=>1 ); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; @@ -225,6 +236,12 @@ returns the error, otherwise returns false. sub replace { my $self = shift; + my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') ) + ? shift + : $self->replace_old; + + $self->$_( $self->$_ || $old->$_ ) for qw( _password _password_encoding ); + local $SIG{INT} = 'IGNORE'; local $SIG{QUIT} = 'IGNORE'; local $SIG{TERM} = 'IGNORE'; @@ -235,7 +252,7 @@ sub replace { local $FS::UID::AutoCommit = 0; my $dbh = dbh; - my $error = $self->SUPER::replace(@_); + my $error = $self->SUPER::replace($old); if ( $error ) { $dbh->rollback if $oldAutoCommit; return $error; @@ -264,7 +281,7 @@ sub replace { } } - if ( defined($self->get('emailaddress')) ) { + if ( defined($self->hashref->{'emailaddress'}) ) { #ineffecient but whatever, how many email addresses can there be? @@ -302,6 +319,19 @@ sub replace { } #} + if ( ( $old->selfservice_access eq '' && $self->selfservice_access + && ! $self->_password + ) + || $self->_resend() + ) + { + my $error = $self->send_reset_email( queue=>1 ); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; @@ -383,6 +413,11 @@ and replace methods. sub check { my $self = shift; + if ( $self->selfservice_access eq 'R' ) { + $self->selfservice_access('Y'); + $self->_resend('Y'); + } + my $error = $self->ut_numbern('contactnum') || $self->ut_foreign_keyn('prospectnum', 'prospect_main', 'prospectnum') @@ -425,6 +460,155 @@ sub contact_classname { $contact_class->classname; } +sub by_selfservice_email { + my($class, $email) = @_; + + my $contact_email = qsearchs({ + 'table' => 'contact_email', + 'addl_from' => ' LEFT JOIN contact USING ( contactnum ) ', + 'hashref' => { 'emailaddress' => $email, }, + 'extra_sql' => " AND selfservice_access = 'Y' ". + " AND ( disabled IS NULL OR disabled = '' )", + }) or return ''; + +warn $contact_email; + + $contact_email->contact; + +} + +#these three functions are very much false laziness w/FS/FS/Auth/internal.pm +# and should maybe be libraried in some way for other password needs + +use Crypt::Eksblowfish::Bcrypt qw( bcrypt_hash en_base64 de_base64); + +sub authenticate_password { + my($self, $check_password) = @_; + + if ( $self->_password_encoding eq 'bcrypt' ) { + + my( $cost, $salt, $hash ) = split(',', $self->_password); + + my $check_hash = en_base64( bcrypt_hash( { key_nul => 1, + cost => $cost, + salt => de_base64($salt), + }, + $check_password + ) + ); + + $hash eq $check_hash; + + } else { + + return 0 if $self->_password eq ''; + + $self->_password eq $check_password; + + } + +} + +sub change_password { + my($self, $new_password) = @_; + + $self->change_password_fields( $new_password ); + + $self->replace; + +} + +sub change_password_fields { + my($self, $new_password) = @_; + + $self->_password_encoding('bcrypt'); + + my $cost = 8; + + my $salt = pack( 'C*', map int(rand(256)), 1..16 ); + + my $hash = bcrypt_hash( { key_nul => 1, + cost => $cost, + salt => $salt, + }, + $new_password, + ); + + $self->_password( + join(',', $cost, en_base64($salt), en_base64($hash) ) + ); + +} + +# end of false laziness w/FS/FS/Auth/internal.pm + + +#false laziness w/ClientAPI/MyAccount/reset_passwd +use Digest::SHA qw(sha512_hex); +use FS::Conf; +use FS::ClientAPI_SessionCache; +sub send_reset_email { + my( $self, %opt ) = @_; + + my @contact_email = $self->contact_email or return ''; + + my $reset_session = { + 'contactnum' => $self->contactnum, + 'svcnum' => $opt{'svcnum'}, + }; + + my $timeout = '24 hours'; #? + + my $reset_session_id; + do { + $reset_session_id = sha512_hex(time(). {}. rand(). $$) + } until ( ! defined $self->myaccount_cache->get("reset_passwd_$reset_session_id") ); + #just in case + + $self->myaccount_cache->set( "reset_passwd_$reset_session_id", $reset_session, $timeout ); + + #email it + + my $conf = new FS::Conf; + + my $cust_main = $self->cust_main + or die "no customer"; #reset a password for a prospect contact? someday + + my $msgnum = $conf->config('selfservice-password_reset_msgnum', $cust_main->agentnum); + #die "selfservice-password_reset_msgnum unset" unless $msgnum; + return { 'error' => "selfservice-password_reset_msgnum unset" } unless $msgnum; + my $msg_template = qsearchs('msg_template', { msgnum => $msgnum } ); + my %msg_template = ( + 'to' => join(',', map $_->emailaddress, @contact_email ), + 'cust_main' => $cust_main, + 'object' => $self, + 'substitutions' => { 'session_id' => $reset_session_id } + ); + + if ( $opt{'queue'} ) { #or should queueing just be the default? + + my $queue = new FS::queue { + 'job' => 'FS::Misc::process_send_email', + 'custnum' => $cust_main->custnum, + }; + $queue->insert( $msg_template->prepare( %msg_template ) ); + + } else { + + $msg_template->send( %msg_template ); + + } + +} + +use vars qw( $myaccount_cache ); +sub myaccount_cache { + #my $class = shift; + $myaccount_cache ||= new FS::ClientAPI_SessionCache( { + 'namespace' => 'FS::ClientAPI::MyAccount', + } ); +} + =back =head1 BUGS diff --git a/FS/FS/msg_template.pm b/FS/FS/msg_template.pm index 588592175..2fc66b478 100644 --- a/FS/FS/msg_template.pm +++ b/FS/FS/msg_template.pm @@ -559,6 +559,9 @@ sub substitutions { [ company_phonenum => sub { $conf->config('company_phonenum', shift->agentnum) } ], + [ selfservice_server_base_url => sub { + $conf->config('selfservice_server-base_url') #, shift->agentnum) + } ], ], # next_bill_date 'cust_pkg' => [qw( @@ -697,6 +700,10 @@ Returns the L object for this template. sub _upgrade_data { my ($self, %opts) = @_; + ### + # First move any historical templates in config to real message templates + ### + my @fixes = ( [ 'alerter_msgnum', 'alerter_template', '', '', '' ], [ 'cancel_msgnum', 'cancelmessage', 'cancelsubject', '', '' ], @@ -788,6 +795,11 @@ sub _upgrade_data { } # if alerter_msgnum } + + ### + # Move subject and body from msg_template to template_content + ### + foreach my $msg_template ( qsearch('msg_template', {}) ) { if ( $msg_template->subject || $msg_template->body ) { # create new default content @@ -811,6 +823,35 @@ sub _upgrade_data { die $error if $error; } } + + ### + # Add new-style default templates if missing + ### + $self->_populate_initial_data; + +} + +sub _populate_initial_data { #class method + #my($class, %opts) = @_; + #my $class = shift; + + eval "use FS::msg_template::InitialData;"; + die $@ if $@; + + my $initial_data = FS::msg_template::InitialData->_initial_data; + + foreach my $hash ( @$initial_data ) { + + next if $hash->{_conf} && $conf->config( $hash->{_conf} ); + + my $msg_template = new FS::msg_template($hash); + my $error = $msg_template->insert( @{ $hash->{_insert_args} || [] } ); + die $error if $error; + + $conf->set( $hash->{_conf}, $msg_template->msgnum ) if $hash->{_conf}; + + } + } sub eviscerate { diff --git a/fs_selfservice/FS-SelfService/cgi/do_forgot_password.html b/fs_selfservice/FS-SelfService/cgi/do_forgot_password.html new file mode 100644 index 000000000..adf8ccac8 --- /dev/null +++ b/fs_selfservice/FS-SelfService/cgi/do_forgot_password.html @@ -0,0 +1,18 @@ + + + Forgot password + <%= $head %> + + + <%= $body_header %> + +Forgot password

+<%= $error %> + +<%= if (!$error) { + $OUT .= 'A verification email has been sent to your mailbox. Please follow + the link in your email to complete your password reset.'; + } +%> + +<%= $body_footer %> diff --git a/fs_selfservice/FS-SelfService/cgi/do_process_forgot_password.html b/fs_selfservice/FS-SelfService/cgi/do_process_forgot_password.html new file mode 100644 index 000000000..9274f9294 --- /dev/null +++ b/fs_selfservice/FS-SelfService/cgi/do_process_forgot_password.html @@ -0,0 +1,18 @@ + + + Reset password + <%= $head %> + + + <%= $body_header %> + +Reset password

+<%= $error %> + +<%= if (!$error) { + $self_url =~ s/\?.*//; + $OUT .= "Your password has been changed. You can now log in."; + } +%> + +<%= $body_footer %> diff --git a/fs_selfservice/FS-SelfService/cgi/forgot_password.html b/fs_selfservice/FS-SelfService/cgi/forgot_password.html new file mode 100644 index 000000000..e14034c7d --- /dev/null +++ b/fs_selfservice/FS-SelfService/cgi/forgot_password.html @@ -0,0 +1,53 @@ + + + Forgot password + <%= $head %> + + + <%= $body_header %> + +Forgot password

+<%= $error %> + +
+ + + +Please enter your email address. A password reset email will be sent to that address. + + + + + + + + +<%= +if ( $single_domain ) { + + $OUT .= qq(); + +} else { + + $OUT .= qq( + + + + + ); + +} + +%> + + + + +
Email address + <%= $single_domain ? '@'.$single_domain : '' %> +
Domain + +
+
+ +<%= $body_footer %> diff --git a/fs_selfservice/FS-SelfService/cgi/login.html b/fs_selfservice/FS-SelfService/cgi/login.html index 68f3ae465..65efd7bdb 100644 --- a/fs_selfservice/FS-SelfService/cgi/login.html +++ b/fs_selfservice/FS-SelfService/cgi/login.html @@ -15,25 +15,26 @@ - - - - - <%= if ( $single_domain ) { - $OUT .= qq(); + $OUT .= qq( + + + + + + ); } else { $OUT .= qq( - + ); diff --git a/fs_selfservice/FS-SelfService/cgi/process_forgot_password.html b/fs_selfservice/FS-SelfService/cgi/process_forgot_password.html new file mode 100644 index 000000000..3d8c0583e --- /dev/null +++ b/fs_selfservice/FS-SelfService/cgi/process_forgot_password.html @@ -0,0 +1,44 @@ + + + Reset password + <%= $head %> + + + <%= $body_header %> + +Reset password

+<%= $error %> + + + + + + +
Username - <%= $single_domain ? '@'.$single_domain : '' %> -
Username + \@$single_domain +
Domain Email address - +
+ +<%= if (!$error) { + + $OUT .= <<'END'; + + + + + + + + + + + + + + +END + + } +%> + +
New password:
Re-enter new password:
+ + +<%= $body_footer %> diff --git a/fs_selfservice/FS-SelfService/cgi/selfservice.cgi b/fs_selfservice/FS-SelfService/cgi/selfservice.cgi index ea2a40bfa..8d3a23b03 100755 --- a/fs_selfservice/FS-SelfService/cgi/selfservice.cgi +++ b/fs_selfservice/FS-SelfService/cgi/selfservice.cgi @@ -22,6 +22,7 @@ use FS::SelfService qw( adjust_ticket_priority mason_comp port_graph start_thirdparty finish_thirdparty + reset_passwd check_reset_passwd process_reset_passwd ); $template_dir = '.'; @@ -42,54 +43,68 @@ if ( exists($cookies{'session'}) ) { if ( $session_id eq 'login' ) { # then we've just come back from the login page - $cgi->param('username') =~ /^\s*([a-z0-9_\-\.\&]{0,$form_max})\s*$/i; - my $username = $1; - - $cgi->param('domain') =~ /^\s*([\w\-\.]{0,$form_max})\s*$/; - my $domain = $1; - $cgi->param('password') =~ /^(.{0,$form_max})$/; my $password = $1; - if ( $username and $domain and $password ) { + if ( $cgi->param('email') =~ /^\s*([a-z0-9_\-\.\@]{1,$form_max})\s*$/i ) { - # authenticate + my $email = $1; $login_rv = login( - 'username' => $username, - 'domain' => $domain, - 'password' => $password, + 'email' => $email, + 'password' => $password ); $session_id = $login_rv->{'session_id'}; - } elsif ( $username or $domain or $password ) { + } else { + + $cgi->param('username') =~ /^\s*([a-z0-9_\-\.\&]{0,$form_max})\s*$/i; + my $username = $1; + + $cgi->param('domain') =~ /^\s*([\w\-\.]{0,$form_max})\s*$/; + my $domain = $1; + + if ( $username and $domain and $password ) { + + # authenticate + $login_rv = login( + 'username' => $username, + 'domain' => $domain, + 'password' => $password, + ); + $session_id = $login_rv->{'session_id'}; + + } elsif ( $username or $domain or $password ) { - my $error = 'Illegal '; #XXX localization... - my $count = 0; - if ( !$username ) { - $error .= 'username'; - $count++; - } - if ( !$domain ) { - $error .= ', ' if $count; - $error .= 'domain'; - $count++; - } - if ( !$password ) { - $error .= ', ' if $count; - $error .= 'and ' if $count > 1; - $error .= 'password'; - $count++; + my $error = 'Illegal '; #XXX localization... + my $count = 0; + if ( !$username ) { + $error .= 'username'; + $count++; + } + if ( !$domain ) { + $error .= ', ' if $count; + $error .= 'domain'; + $count++; + } + if ( !$password ) { + $error .= ', ' if $count; + $error .= 'and ' if $count > 1; + $error .= 'password'; + $count++; + } + $error .= '.'; + $login_rv = { + 'username' => $username, + 'domain' => $domain, + 'password' => $password, + 'error' => $error, + }; + $session_id = undef; # attempt login again + } - $error .= '.'; - $login_rv = { - 'username' => $username, - 'domain' => $domain, - 'password' => $password, - 'error' => $error, - }; - $session_id = undef; # attempt login again } # else there was no input, so show no error message + } # else session_id ne 'login' } else { @@ -157,6 +172,10 @@ my @actions = ( qw( real_port_graph change_password process_change_password + forgot_password + do_forgot_password + process_forgot_password + do_process_forgot_password customer_suspend_pkg process_suspend_pkg )); @@ -991,6 +1010,31 @@ sub process_change_password { } +sub forgot_password { + login_info( 'agentnum' => scalar($cgi->param('agentnum')) ); #skin_info +} + +sub do_forgot_password { + reset_passwd( + map { $_ => scalar($cgi->param($_)) } + qw( email username domain ) + ); +} + +sub process_forgot_password { + check_reset_passwd( + map { $_ => scalar($cgi->param($_)) } + qw( session_id ) + ); +} + +sub do_process_forgot_password { + process_reset_passwd( + map { $_ => scalar($cgi->param($_)) } + qw( session_id new_password new_password2 ) + ); +} + #-- sub do_template { diff --git a/httemplate/edit/cust_main-contacts.html b/httemplate/edit/cust_main-contacts.html index cd83a2960..9f0654608 100644 --- a/httemplate/edit/cust_main-contacts.html +++ b/httemplate/edit/cust_main-contacts.html @@ -4,7 +4,7 @@ 'post_url' => popurl(1). 'process/cust_main-contacts.html', 'no_pkey_display' => 1, 'labels' => { - 'contactnum' => 'Contact', + 'contactnum' => ' ', #'Contact', #'locationnum' => ' ', }, 'fields' => [ @@ -13,7 +13,7 @@ 'colspan' => 6, 'm2m_method' => 'cust_contact', 'm2m_dstcol' => 'contactnum', - 'm2_label' => 'Contact', + 'm2_label' => ' ', #'Contact', 'm2_error_callback' => $m2_error_callback, }, ], diff --git a/httemplate/edit/msg_template.html b/httemplate/edit/msg_template.html index 06cac440e..6c9d2f414 100644 --- a/httemplate/edit/msg_template.html +++ b/httemplate/edit/msg_template.html @@ -209,6 +209,7 @@ my %substitutions = ( '$company_name' => 'Our company name', '$company_address'=> 'Our company address', '$company_phonenum' => 'Our phone number', + '$selfservice_server_base_url' => 'Base URL of customer self-service', ], 'contact' => [ # duplicate this for shipping '$name' => 'Company and contact name', diff --git a/httemplate/edit/process/cust_main-contacts.html b/httemplate/edit/process/cust_main-contacts.html index ed874a55e..d902dee09 100644 --- a/httemplate/edit/process/cust_main-contacts.html +++ b/httemplate/edit/process/cust_main-contacts.html @@ -12,7 +12,9 @@ %> <%init> -my @contact_fields = qw( classnum first last title comment emailaddress ); +my @contact_fields = qw( + classnum first last title comment emailaddress selfservice_access +); foreach my $phone_type ( qsearch({table=>'phone_type', order_by=>'weight'}) ) { push @contact_fields, 'phonetypenum'.$phone_type->phonetypenum; } diff --git a/httemplate/elements/contact.html b/httemplate/elements/contact.html index 8abce058e..979c26b49 100644 --- a/httemplate/elements/contact.html +++ b/httemplate/elements/contact.html @@ -45,14 +45,30 @@ % } - " - <% $onchange %> - >
+% if ( $field eq 'selfservice_access' ) { + + +% } else { + " + <% $onchange %> + > +% } +
<% $label{$field} %> % } @@ -64,6 +80,9 @@ my( %opt ) = @_; +my $conf = new FS::Conf; +my $self_base_url = $conf->config('selfservice_server-base_url'); + my $name = $opt{'element_name'} || $opt{'field'} || 'contactnum'; my $id = $opt{'id'} || 'contactnum'; @@ -90,10 +109,11 @@ if ( $curr_value ) { my %size = ( 'title' => 12 ); tie my %label, 'Tie::IxHash', - 'first' => 'First name', - 'last' => 'Last name', - 'title' => 'Title/Position', - 'emailaddress' => 'Email', + 'first' => 'First name', + 'last' => 'Last name', + 'title' => 'Title/Position', + 'emailaddress' => 'Email', + 'selfservice_access' => 'Self-service' ; my $first = 0; diff --git a/httemplate/view/cust_main/contacts_new.html b/httemplate/view/cust_main/contacts_new.html index a851d99ed..f73483ae1 100644 --- a/httemplate/view/cust_main/contacts_new.html +++ b/httemplate/view/cust_main/contacts_new.html @@ -1,31 +1,56 @@
Contacts Edit contacts - + +<& /elements/table-grid.html &> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor = $bgcolor2; + + + + + +% foreach my $phone_type (@phone_type) { + + % foreach my $contact ( @contacts ) { -% #XXX maybe this should be a table with alternating colors instead - - + + % my @contact_email = $contact->contact_email; -% if (@contact_email) { - - -% } + + + % foreach my $phone_type (@phone_type) { % my $contact_phone = % qsearchs('contact_phone', { % 'contactnum' => $contact->contactnum, % 'phonetypenum' => $phone_type->phonetypenum, -% }) -% or next; - - +% }); + % } + +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } % }
TypeContactEmailSelf-service<% $phone_type->typename |h %> phone +% } +
<% $contact->contact_classname %> Contact<% $contact->line %><% $contact->contact_classname |h %><% $contact->line |h %>   Email<% join(', ', map $_->emailaddress, @contact_email) %><% join(', ', map $_->emailaddress, @contact_email) %> +% if ( $contact->selfservice_access ) { + Enabled +%# disable +%# re-email +% } else { + Disabled +%# enable +% } +    <% $phone_type->typename %> phone<% $contact_phone->phonenum_pretty |h %><% $contact_phone ? $contact_phone->phonenum_pretty : '' |h %>
<%once>