summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Kohler <ivan@freeside.biz>2013-12-30 01:43:16 -0800
committerIvan Kohler <ivan@freeside.biz>2013-12-30 01:43:16 -0800
commitcb6cca67db487271ce96b49289ada58691a2067d (patch)
tree003581386f08c392c03ea7757907fa7d0992d857
parent6b4a2501a75964c864467a3bf85bbba039009049 (diff)
self-service access for contacts, RT#25533
-rw-r--r--FS/FS/ClientAPI/MyAccount.pm312
-rw-r--r--FS/FS/Conf.pm3
-rw-r--r--FS/FS/Setup.pm3
-rw-r--r--FS/FS/contact.pm188
-rw-r--r--FS/FS/msg_template.pm41
-rw-r--r--fs_selfservice/FS-SelfService/cgi/do_forgot_password.html18
-rw-r--r--fs_selfservice/FS-SelfService/cgi/do_process_forgot_password.html18
-rw-r--r--fs_selfservice/FS-SelfService/cgi/forgot_password.html53
-rw-r--r--fs_selfservice/FS-SelfService/cgi/login.html21
-rw-r--r--fs_selfservice/FS-SelfService/cgi/process_forgot_password.html44
-rwxr-xr-xfs_selfservice/FS-SelfService/cgi/selfservice.cgi116
-rw-r--r--httemplate/edit/cust_main-contacts.html4
-rw-r--r--httemplate/edit/msg_template.html1
-rw-r--r--httemplate/edit/process/cust_main-contacts.html4
-rw-r--r--httemplate/elements/contact.html44
-rw-r--r--httemplate/view/cust_main/contacts_new.html49
16 files changed, 769 insertions, 150 deletions
diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm
index aa21ac0..37d21ea 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 eb47413..375d2d8 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 e27b66f..29130fa 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 3f73bd6..2a73b9c 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 5885921..2fc66b4 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<FS::agent> 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 0000000..adf8cca
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/do_forgot_password.html
@@ -0,0 +1,18 @@
+<HTML>
+ <HEAD>
+ <TITLE>Forgot password</TITLE>
+ <%= $head %>
+ </HEAD>
+ <BODY BGCOLOR="<%= $body_bgcolor || '#eeeeee' %>">
+ <%= $body_header %>
+
+<FONT SIZE=5>Forgot password</FONT><BR><BR>
+<FONT SIZE="+1" COLOR="#ff0000"><%= $error %></FONT>
+
+<%= 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 0000000..9274f92
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/do_process_forgot_password.html
@@ -0,0 +1,18 @@
+<HTML>
+ <HEAD>
+ <TITLE>Reset password</TITLE>
+ <%= $head %>
+ </HEAD>
+ <BODY BGCOLOR="<%= $body_bgcolor || '#eeeeee' %>">
+ <%= $body_header %>
+
+<FONT SIZE=5>Reset password</FONT><BR><BR>
+<FONT SIZE="+1" COLOR="#ff0000"><%= $error %></FONT>
+
+<%= if (!$error) {
+ $self_url =~ s/\?.*//;
+ $OUT .= "Your password has been changed. You can now <A HREF=\"$self_url\">log in</A>.";
+ }
+%>
+
+<%= $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 0000000..e14034c
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/forgot_password.html
@@ -0,0 +1,53 @@
+<HTML>
+ <HEAD>
+ <TITLE>Forgot password</TITLE>
+ <%= $head %>
+ </HEAD>
+ <BODY BGCOLOR="<%= $body_bgcolor || '#eeeeee' %>">
+ <%= $body_header %>
+
+<FONT SIZE=5>Forgot password</FONT><BR><BR>
+<FONT SIZE="+1" COLOR="#ff0000"><%= $error %></FONT>
+
+<FORM ACTION="<%= $self_url %>" METHOD=POST>
+<INPUT TYPE="hidden" NAME="action" VALUE="do_forgot_password">
+<INPUT TYPE="hidden" NAME="agentnum" VALUE="<%= $agentnum %>">
+
+Please enter your email address. A password reset email will be sent to that address.
+
+<TABLE BGCOLOR="<%= $box_bgcolor || '#c0c0c0' %>" BORDER=0 CELLSPACING=2 CELLPADDING=0>
+
+<TR>
+ <TH ALIGN="right">Email address </TH>
+ <TD>
+ <INPUT TYPE="text" NAME="email" VALUE="<%= $username %>"><%= $single_domain ? '@'.$single_domain : '' %>
+ </TD>
+</TR>
+
+<%=
+if ( $single_domain ) {
+
+ $OUT .= qq(<INPUT TYPE="hidden" NAME="domain" VALUE="$single_domain">);
+
+} else {
+
+ $OUT .= qq(
+ <TR>
+ <TH ALIGN="right">Domain </TH>
+ <TD>
+ <INPUT TYPE="text" NAME="domain" VALUE="$domain">
+ </TD>
+ </TR>
+ );
+
+}
+
+%>
+
+<TR>
+ <TD COLSPAN=2 ALIGN="center"><INPUT TYPE="submit" VALUE="Send reset email"></TD>
+</TR>
+</TABLE>
+</FORM>
+
+<%= $body_footer %>
diff --git a/fs_selfservice/FS-SelfService/cgi/login.html b/fs_selfservice/FS-SelfService/cgi/login.html
index 68f3ae4..65efd7b 100644
--- a/fs_selfservice/FS-SelfService/cgi/login.html
+++ b/fs_selfservice/FS-SelfService/cgi/login.html
@@ -15,25 +15,26 @@
<TABLE BGCOLOR="<%= $box_bgcolor || '#c0c0c0' %>" BORDER=0 CELLSPACING=2 CELLPADDING=0>
-<TR>
- <TH ALIGN="right">Username </TH>
- <TD>
- <INPUT TYPE="text" NAME="username" VALUE="<%= $username %>"><%= $single_domain ? '@'.$single_domain : '' %>
- </TD>
-</TR>
-
<%=
if ( $single_domain ) {
- $OUT .= qq(<INPUT TYPE="hidden" NAME="domain" VALUE="$single_domain">);
+ $OUT .= qq(
+ <TR>
+ <TH ALIGN="right">Username </TH>
+ <TD>
+ <INPUT TYPE="text" NAME="username" VALUE="$username">\@$single_domain
+ </TD>
+ </TR>
+ <INPUT TYPE="hidden" NAME="domain" VALUE="$single_domain">
+ );
} else {
$OUT .= qq(
<TR>
- <TH ALIGN="right">Domain </TH>
+ <TH ALIGN="right">Email address </TH>
<TD>
- <INPUT TYPE="text" NAME="domain" VALUE="$domain">
+ <INPUT TYPE="text" NAME="email" VALUE="$email">
</TD>
</TR>
);
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 0000000..3d8c058
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/process_forgot_password.html
@@ -0,0 +1,44 @@
+<HTML>
+ <HEAD>
+ <TITLE>Reset password</TITLE>
+ <%= $head %>
+ </HEAD>
+ <BODY BGCOLOR="<%= $body_bgcolor || '#eeeeee' %>">
+ <%= $body_header %>
+
+<FONT SIZE=5>Reset password</FONT><BR><BR>
+<FONT SIZE="+1" COLOR="#ff0000"><%= $error %></FONT>
+
+<FORM ACTION="<%= $self_url %>" METHOD=POST>
+<INPUT TYPE="hidden" NAME="action" VALUE="do_process_forgot_password">
+<INPUT TYPE="hidden" NAME="session_id" VALUE="<%= $session_id %>">
+<INPUT TYPE="hidden" NAME="agentnum" VALUE="<%= $agentnum %>">
+
+<TABLE BGCOLOR="<%= $box_bgcolor || '#c0c0c0' %>" BORDER=0 CELLSPACING=2 CELLPADDING=0>
+
+<%= if (!$error) {
+
+ $OUT .= <<'END';
+
+ <TR>
+ <TH ALIGN="right">New password: </TH>
+ <TD><INPUT TYPE="password" NAME="new_password" SIZE="18"></TD>
+ </TR>
+
+ <TR>
+ <TH ALIGN="right">Re-enter new password: </TH>
+ <TD><INPUT TYPE="password" NAME="new_password2" SIZE="18"></TD>
+ </TR>
+
+ <TR>
+ <TD COLSPAN=2 ALIGN="center"><INPUT TYPE="submit" VALUE="Change password"></TD>
+ </TR>
+END
+
+ }
+%>
+
+</TABLE>
+</FORM>
+
+<%= $body_footer %>
diff --git a/fs_selfservice/FS-SelfService/cgi/selfservice.cgi b/fs_selfservice/FS-SelfService/cgi/selfservice.cgi
index ea2a40b..8d3a23b 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 cd83a29..9f06546 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' => '&nbsp;',
},
'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 06cac44..6c9d2f4 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 ed874a5..d902dee 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 8abce05..979c26b 100644
--- a/httemplate/elements/contact.html
+++ b/httemplate/elements/contact.html
@@ -45,14 +45,30 @@
% }
<TD>
- <INPUT TYPE = "text"
- NAME = "<%$name%>_<%$field%>"
- ID = "<%$id%>_<%$field%>"
- SIZE = "<% $size{$field} || 15 %>"
- VALUE = "<% scalar($cgi->param($name."_$field"))
- || $value |h %>"
- <% $onchange %>
- ><BR>
+% if ( $field eq 'selfservice_access' ) {
+ <SELECT NAME = "<%$name%>_<%$field%>"
+ ID = "<%$id%>_<%$field%>"
+ >
+ <OPTION VALUE="">Disabled
+% if ( $value || $self_base_url ) {
+ <OPTION VALUE="Y" <% $value eq 'Y' ? 'SELECTED' : '' %>>Enabled
+% if ( $value eq 'Y' && $self_base_url ) {
+ <OPTION VALUE="R">Re-email
+% }
+% }
+ </SELECT>
+
+% } else {
+ <INPUT TYPE = "text"
+ NAME = "<%$name%>_<%$field%>"
+ ID = "<%$id%>_<%$field%>"
+ SIZE = "<% $size{$field} || 14 %>"
+ VALUE = "<% scalar($cgi->param($name."_$field"))
+ || $value |h %>"
+ <% $onchange %>
+ >
+% }
+ <BR>
<FONT SIZE="-1"><% $label{$field} %></FONT>
</TD>
% }
@@ -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 a851d99..f73483a 100644
--- a/httemplate/view/cust_main/contacts_new.html
+++ b/httemplate/view/cust_main/contacts_new.html
@@ -1,31 +1,56 @@
<BR>
<FONT CLASS="fsinnerbox-title">Contacts</FONT>
<A HREF="<%$p%>edit/cust_main-contacts.html?<% $cust_main->custnum %>">Edit contacts</A>
-<TABLE CLASS="fsinnerbox">
+
+<& /elements/table-grid.html &>
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = $bgcolor2;
+<TR>
+ <TH CLASS="grid" ALIGN="left" BGCOLOR="#cccccc">Type</TH>
+ <TH CLASS="grid" ALIGN="left" BGCOLOR="#cccccc">Contact</TH>
+ <TH CLASS="grid" ALIGN="left" BGCOLOR="#cccccc">Email</TH>
+ <TH CLASS="grid" ALIGN="left" BGCOLOR="#cccccc">Self-service</TH>
+% foreach my $phone_type (@phone_type) {
+ <TH CLASS="grid" ALIGN="left" BGCOLOR="#cccccc"><% $phone_type->typename |h %> phone</TD>
+% }
+</TR>
+
% foreach my $contact ( @contacts ) {
-% #XXX maybe this should be a table with alternating colors instead
<TR>
- <TD ALIGN="right"><% $contact->contact_classname %> Contact</TD>
- <TD BGCOLOR="#FFFFFF"><% $contact->line %></TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $contact->contact_classname |h %></TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $contact->line |h %></TD>
% my @contact_email = $contact->contact_email;
-% if (@contact_email) {
- <TD ALIGN="right">&nbsp;&nbsp;&nbsp;Email</TD>
- <TD BGCOLOR="#FFFFFF"><% join(', ', map $_->emailaddress, @contact_email) %></TD>
-% }
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% join(', ', map $_->emailaddress, @contact_email) %></TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% if ( $contact->selfservice_access ) {
+ Enabled
+%# <FONT SIZE="-1"><A HREF="XXX">disable</A>
+%# <A HREF="XXX">re-email</A></FONT>
+% } else {
+ Disabled
+%# <FONT SIZE="-1"><A HREF="XXX">enable</A></FONT>
+% }
+ </TD>
% foreach my $phone_type (@phone_type) {
% my $contact_phone =
% qsearchs('contact_phone', {
% 'contactnum' => $contact->contactnum,
% 'phonetypenum' => $phone_type->phonetypenum,
-% })
-% or next;
- <TD ALIGN="right">&nbsp;&nbsp;&nbsp;<% $phone_type->typename %> phone</TD>
- <TD BGCOLOR="#FFFFFF"><% $contact_phone->phonenum_pretty |h %></TD>
+% });
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $contact_phone ? $contact_phone->phonenum_pretty : '' |h %></TD>
% }
</TR>
+
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
% }
</TABLE>
<%once>