double process / back button protection for self-service payments, RT#29168
[freeside.git] / fs_selfservice / FS-SelfService / cgi / selfservice.cgi
index ea2a40b..2b4bb43 100755 (executable)
@@ -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 = '.';
@@ -31,85 +32,6 @@ $DEBUG = 0;
 $form_max = 255;
 
 $cgi = new CGI;
-my %cookies = CGI::Cookie->fetch;
-
-my $login_rv;
-
-if ( exists($cookies{'session'}) ) {
-
-  $session_id = $cookies{'session'}->value;
-
-  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 ) {
-
-      # 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++;
-      }
-      $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 {
-  # there is no session cookie
-  $login_rv = {};
-}
-
-if ( !$session_id ) {
-  # XXX why are we getting agentnum from a CGI param? surely it should 
-  # be some kind of configuration option.
-  #
-  # show the login page
-  $session_id = 'login'; # set state
-  my $login_info = login_info( 'agentnum' => scalar($cgi->param('agentnum')) );
-
-  do_template('login', { %$login_rv, %$login_info });
-  exit;
-}
-
-# at this point $session_id is a real session
 
 #order|pw_list XXX ???
 my @actions = ( qw(
@@ -161,6 +83,15 @@ my @actions = ( qw(
   process_suspend_pkg
 ));
 
+my @nologin_actions = (qw(
+  forgot_password
+  do_forgot_password
+  process_forgot_password
+  do_process_forgot_password
+));
+push @actions, @nologin_actions;
+my %nologin_actions = map { $_=>1 } @nologin_actions;
+
 my $action = 'myaccount'; # sensible default
 if ( $cgi->param('action') =~ /^(\w+)$/ ) {
   if (grep {$_ eq $1} @actions) {
@@ -170,6 +101,98 @@ if ( $cgi->param('action') =~ /^(\w+)$/ ) {
   }
 }
 
+unless ( $nologin_actions{$action} ) {
+
+  my %cookies = CGI::Cookie->fetch;
+
+  my $login_rv = {};
+
+  if ( exists($cookies{'session'}) ) {
+
+    $session_id = $cookies{'session'}->value;
+
+    if ( $session_id eq 'login' ) {
+      # then we've just come back from the login page
+
+      $cgi->param('password') =~ /^(.{0,$form_max})$/;
+      my $password = $1;
+
+      if ( $cgi->param('email') =~ /^\s*([a-z0-9_\-\.\@]{1,$form_max})\s*$/i ) {
+
+        my $email = $1;
+        $login_rv = login(
+          'email'    => $email,
+          'password' => $password
+        );
+        $session_id = $login_rv->{'session_id'};
+
+      } 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++;
+          }
+          $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 there is no session cookie
+
+  if ( !$session_id ) {
+    # show the login page
+    $session_id = 'login'; # set state
+    my $login_info = login_info( 'agentnum' => scalar($cgi->param('agentnum')) );
+
+    do_template('login', { %$login_rv, %$login_info });
+    exit;
+  }
+
+  # at this point $session_id is a real session
+
+}
+
 warn "calling $action sub\n"
   if $DEBUG;
 $FS::SelfService::DEBUG = $DEBUG;
@@ -182,7 +205,7 @@ if ( $result->{error} && ( $result->{error} eq "Can't resume session"
   || $result->{error} eq "Expired session") ) { #ick
 
   $session_id = 'login';
-  my $login_info = login_info();
+  my $login_info = login_info( 'agentnum' => scalar($cgi->param('agentnum')) );
   do_template('login', $login_info);
   exit;
 }
@@ -604,7 +627,10 @@ sub payment_results {
   my $auto = 0;
   $auto = 1 if $cgi->param('auto');
 
-  $cgi->param('paybatch') =~ /^([\w\-\.]+)$/ or die "illegal paybatch";
+  $cgi->param('payunique') =~ /^([\w\-\.]*)$/ or die "illegal payunique";
+  my $payunique = $1;
+
+  $cgi->param('paybatch') =~ /^([\w\-\.]*)$/ or die "illegal paybatch";
   my $paybatch = $1;
 
   $cgi->param('discount_term') =~ /^(\d*)$/ or die "illegal discount_term";
@@ -628,6 +654,7 @@ sub payment_results {
     'country'    => $country,
     'save'       => $save,
     'auto'       => $auto,
+    'payunique'  => $payunique,
     'paybatch'   => $paybatch,
     'discount_term' => $discount_term,
   );
@@ -991,6 +1018,31 @@ sub process_change_password {
 
 }
 
+sub forgot_password {
+  login_info( 'agentnum' => scalar($cgi->param('agentnum')) );
+}
+
+sub do_forgot_password {
+  reset_passwd(
+    map { $_ => scalar($cgi->param($_)) }
+      qw( agentnum 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 {
@@ -1008,10 +1060,10 @@ sub do_template {
   $fill_in->{$_} = $access_info->{$_} foreach keys %$access_info;
 
   # update the user's authentication
-  my $timeout = $access_info->{'timeout'} || '60';
+  my $timeout = $access_info->{'timeout'} || '3600';
   my $cookie = CGI::Cookie->new('-name'     => 'session',
                                 '-value'    => $session_id,
-                                '-expires'  => '+'.$timeout,
+                                '-expires'  => '+'.$timeout.'s',
                                 #'-secure'   => 1, # would be a good idea...
                                );
   if ( $name eq 'logout' ) {