KaiRo bug 393 - Create a grouping mechanism for user names so people with multiple...
authorRobert Kaiser <kairo@kairo.at>
Wed, 16 Nov 2016 19:29:13 +0000 (20:29 +0100)
committerRobert Kaiser <kairo@kairo.at>
Wed, 16 Nov 2016 19:29:13 +0000 (20:29 +0100)
authorize.php
authsystem.css
authsystem.inc.php
authsystem.js
authutils.php-class
index.php

index 114064fb2ef396906bad35afefd7453fea7375a4..b4344961d35b95a3d5b408ac30c61617bf5a66f1 100644 (file)
@@ -39,7 +39,7 @@ if (!count($errors)) {
     $session['logged_in'] = 0;
   }
   if (intval($session['user'])) {
-    $result = $db->prepare('SELECT `id`,`email`,`verify_hash` FROM `auth_users` WHERE `id` = :userid;');
+    $result = $db->prepare('SELECT `id`,`email`,`verify_hash`,`group_id` FROM `auth_users` WHERE `id` = :userid;');
     $result->execute(array(':userid' => $session['user']));
     $user = $result->fetch(PDO::FETCH_ASSOC);
     if (!$user['id']) {
@@ -84,8 +84,15 @@ if (!count($errors)) {
       $form = $body->appendForm('', 'POST', 'loginauthform');
       $form->setAttribute('id', 'loginauthform');
       $form->setAttribute('class', 'loginarea');
-      $form->appendInputRadio('user_email', 'uemail_'.md5($user['email']), $user['email'], $user['email'] == $user['email']);
-      $form->appendLabel('uemail_'.md5($user['email']), $user['email']);
+      $ulist = $form->appendElement('ul');
+      $ulist->setAttribute('class', 'flat emaillist');
+      $emails = $utils->getGroupedEmails($user['group_id']);
+      if (!count($emails)) { $emails = array($user['email']); }
+      foreach ($emails as $email) {
+        $litem = $ulist->appendElement('li');
+        $litem->appendInputRadio('user_email', 'uemail_'.md5($email), $email, $email == $user['email']);
+        $litem->appendLabel('uemail_'.md5($email), $email);
+      }
       $para = $form->appendElement('p');
       $para->setAttribute('class', 'small otheremaillinks');
       $link = $para->appendLink('#', _('Add another email address'));
@@ -100,8 +107,19 @@ if (!count($errors)) {
       $para->setAttribute('class', 'small');
       $link = $para->appendLink('#', _('Cancel'));
       $link->setAttribute('id', 'cancelauth'); // Makes the JS put the right functionality onto the link.
+      $utils->setRedirect($session, $_SERVER['REQUEST_URI']);
     }
     else {
+      // Switch to different user if we selected a different email within the group.
+      if (strlen(@$_POST['user_email']) && ($_POST['user_email'] != $user['email'])) {
+        $result = $db->prepare('SELECT `id`, `pwdhash`, `email`, `status`, `verify_hash`,`group_id` FROM `auth_users` WHERE `group_id` = :groupid AND `email` = :email;');
+        $result->execute(array(':groupid' => $user['group_id'], ':email' => $_POST['user_email']));
+        $newuser = $result->fetch(PDO::FETCH_ASSOC);
+        if ($newuser) {
+          $user = $newuser;
+          $session = $utils->getLoginSession($user['id'], $session);
+        }
+      }
       // Handle authorize request, forwarding code in GET parameters if the user has authorized your client.
       $is_authorized = (@$_POST['authorized'] === 'yes');
       $server->handleAuthorizeRequest($request, $response, $is_authorized, $user['id']);
@@ -112,6 +130,7 @@ if (!count($errors)) {
         exit("SUCCESS! Authorization Code: $code");
       }
       */
+      $utils->resetRedirect($session);
       $response->send();
       exit();
     }
@@ -121,12 +140,7 @@ if (!count($errors)) {
     $para = $body->appendElement('p', _('You need to log in or register to continue.'));
     $para->setAttribute('class', 'logininfo');
     $utils->appendLoginForm($body, $session, $user);
-    // Save the request in the session so we can get back to fulfilling it.
-    $result = $db->prepare('UPDATE `auth_sessions` SET `saved_redirect` = :redir WHERE `id` = :sessid;');
-    $saved_request = str_replace('&logout=1', '', $_SERVER['REQUEST_URI']); // Make sure to strip a logout to not get into a loop.
-    if (!$result->execute(array(':redir' => $saved_request, ':sessid' => $session['id']))) {
-      $utils->log('redir_save_failure', 'session: '.$session['id'].', redirect: '.$saved_request);
-    }
+    $utils->setRedirect($session, str_replace('&logout=1', '', $_SERVER['REQUEST_URI'])); // Make sure to strip a logout to not get into a loop.
   }
 }
 
index 4a273f9ce2e1f0fc4f1ddb5a09b08bf545622ca5..4c8dd5f8a787e1949f9d16584d396939a9362e9f 100644 (file)
@@ -94,6 +94,12 @@ table.border td {
   margin: 0.5rem 1rem 0;
 }
 
+.loginheader > .groupmails {
+  font-weight: normal;
+  margin: 5px 0; /* IE8 and older do not support rem */
+  margin: 0.5rem 0;
+}
+
 .loginlinks {
   margin: 5px 10px 0; /* IE8 and older do not support rem */
   margin: 0.5rem 1rem 0;
@@ -106,6 +112,7 @@ table.border td {
 
 .resetinfo,
 .verifyinfo,
+.addemailinfo,
 .newpwdinfo,
 .signinwelcome {
   margin: 5px 10px 0; /* IE8 and older do not support rem */
@@ -133,11 +140,6 @@ table.border td {
 .otheremaillinks > a:link, .otheremaillinks > a:visited { color: #BBBBBB; }
 .otheremaillinks > a:hover, .otheremaillinks > a:active { color: #808080; }
 
-#addanotheremail { /* HACK - not implemented yet */
-  background-color: transparent !important;
-  color: transparent !important;
-}
-
 .small {
   font-size: 0.75em;
 }
index 5354cd9528b4d4a80a2abb5c713c5efd2dd77cdc..17818efe758d995d3eb47660401461f02ec97a02 100644 (file)
@@ -53,6 +53,7 @@ CREATE TABLE `auth_users` (
  `pwdhash` VARCHAR(255) NOT NULL ,
  `status` ENUM('unverified','ok') NOT NULL DEFAULT 'unverified' ,
  `verify_hash` VARCHAR(150) NULL DEFAULT NULL ,
+ `group_id` MEDIUMINT UNSIGNED DEFAULT '0' ,
  PRIMARY KEY (`id`),
  UNIQUE (`email`)
 );
index dee8142337666c5fe1c4b3073a50da4b1a40f019..7a24d84af4b5efde87be187e217d3256c78ffedf 100644 (file)
@@ -33,7 +33,7 @@ window.onload = function() {
   var addAnotherEmail = document.getElementById("addanotheremail");
   if (addAnotherEmail) {
     addAnotherEmail.onclick = function() {
-      // Not implemented yet.
+      location.href = "./?addemail";
     }
   }
   var isNotMe = document.getElementById("isnotme");
index e72768a2bd35c883384d47fec68ff7375a8f5544..9d33f268561bb61ec7511c3a5ab52ee2c9f8d1cb 100755 (executable)
@@ -37,6 +37,18 @@ class AuthUtils {
   // function initSession()
   //   Initialize a session. Returns an associative array of all the DB fields of the session.
   //
+  // function getLoginSession($user)
+  //   Return an associative array of a session with the given user logged in (new if user changed compared to given previous session, otherwise updated variant of that previous session).
+  //
+  // function setRedirect($session, $redirect)
+  //   Set a redirect on the session for performing later. Returns true if a redirect was saved, otherwise false.
+  //
+  // function doRedirectIfSet($session)
+  //   If the session has a redirect set, perform it. Returns true if a redirect was performed, otherwise false.
+  //
+  // function resetRedirect($session)
+  //   If the session has a redirect set, remove it. Returns true if a redirect was removed, otherwise false.
+  //
   // function getDomainBaseURL()
   //   Get the base URL of the current domain, e.g. 'https://example.com'.
   //
@@ -70,8 +82,12 @@ class AuthUtils {
   // function pwdNeedsRehash($user)
   //   Return true if the pwdhash field of the user uses an outdated standard and needs to be rehashed.
   //
-  // function appendLoginForm($dom_element, $session, $user)
-  //   append a login form for the given session to the given DOM element, possibly prefilling the email from the given user info array.
+  // function getGroupedEmails($group_id, [$exclude_email])
+  //   Return all emails grouped in the specified group ID, optionally exclude a specific email (e.g. because you only want non-current entries)
+  //
+  // function appendLoginForm($dom_element, $session, $user, [$addfields])
+  //   Append a login form for the given session to the given DOM element, possibly prefilling the email from the given user info array.
+  //     The optional $addfields parameter is an array of name=>value pairs of hidden fields to add to the form.
 
   function __construct($settings, $db) {
     // *** constructor ***
@@ -159,6 +175,90 @@ class AuthUtils {
     return $session;
   }
 
+  function getLoginSession($userid, $prev_session) {
+    $session = $prev_session;
+    $sesskey = $this->createSessionKey();
+    setcookie('sessionkey', $sesskey, 0, "", "", !$this->running_on_localhost, true); // Last two params are secure and httponly, secure is not set on localhost.
+    // If the previous session has a user set, create a new one - otherwise take existing session entry.
+    if (intval($session['user'])) {
+      $result = $this->db->prepare('INSERT INTO `auth_sessions` (`sesskey`, `time_expire`, `user`, `logged_in`) VALUES (:sesskey, :expire, :userid, TRUE);');
+      $result->execute(array(':sesskey' => $sesskey, ':userid' => $userid, ':expire' => gmdate('Y-m-d H:i:s', strtotime('+1 day'))));
+      // After insert, actually fetch the session row from the DB so we have all values.
+      $result = $this->db->prepare('SELECT * FROM auth_sessions WHERE `sesskey` = :sesskey AND `time_expire` > :expire;');
+      $result->execute(array(':sesskey' => $sesskey, ':expire' => gmdate('Y-m-d H:i:s')));
+      $row = $result->fetch(PDO::FETCH_ASSOC);
+      if ($row) {
+        $session = $row;
+      }
+      else {
+        $utils->log('create_session_failure', 'at login, prev session: '.$session['id'].', new user: '.$userid);
+        $errors[] = _('The session system is not working. Please <a href="https://www.kairo.at/contact">contact KaiRo.at</a> and tell the team about this.');
+      }
+    }
+    else {
+      $result = $this->db->prepare('UPDATE `auth_sessions` SET `sesskey` = :sesskey, `user` = :userid, `logged_in` = TRUE, `time_expire` = :expire WHERE `id` = :sessid;');
+      if (!$result->execute(array(':sesskey' => $sesskey, ':userid' => $userid, ':expire' => gmdate('Y-m-d H:i:s', strtotime('+1 day')), ':sessid' => $session['id']))) {
+        $utils->log('login_failure', 'session: '.$session['id'].', user: '.$userid);
+        $errors[] = _('Login failed unexpectedly. Please <a href="https://www.kairo.at/contact">contact KaiRo.at</a> and tell the team about this.');
+      }
+      else {
+        // After update, actually fetch the session row from the DB so we have all values.
+        $result = $this->db->prepare('SELECT * FROM auth_sessions WHERE `sesskey` = :sesskey AND `time_expire` > :expire;');
+        $result->execute(array(':sesskey' => $sesskey, ':expire' => gmdate('Y-m-d H:i:s')));
+        $row = $result->fetch(PDO::FETCH_ASSOC);
+        if ($row) {
+          $session = $row;
+        }
+      }
+    }
+    return $session;
+  }
+
+  function setRedirect($session, $redirect) {
+    $success = false;
+    // Save the request in the session so we can get back to fulfilling it if one of the links is clicked.
+    $result = $this->db->prepare('UPDATE `auth_sessions` SET `saved_redirect` = :redir WHERE `id` = :sessid;');
+    if (!$result->execute(array(':redir' => $redirect, ':sessid' => $session['id']))) {
+      $this->log('redir_save_failure', 'session: '.$session['id'].', redirect: '.$redirect);
+    }
+    else {
+      $success = true;
+    }
+    return $success;
+  }
+
+  function doRedirectIfSet($session) {
+    $success = false;
+    // If the session has a redirect set, make sure it's performed.
+    if (strlen(@$session['saved_redirect'])) {
+      // Remove redirect.
+      $result = $this->db->prepare('UPDATE `auth_sessions` SET `saved_redirect` = :redir WHERE `id` = :sessid;');
+      if (!$result->execute(array(':redir' => '', ':sessid' => $session['id']))) {
+        $this->log('redir_save_failure', 'session: '.$session['id'].', redirect: (empty)');
+      }
+      else {
+        $success = true;
+      }
+      header('Location: '.$this->getDomainBaseURL().$session['saved_redirect']);
+    }
+    return $success;
+  }
+
+  function resetRedirect($session) {
+    $success = false;
+    // If the session has a redirect set, remove it.
+    if (strlen(@$session['saved_redirect'])) {
+      $result = $this->db->prepare('UPDATE `auth_sessions` SET `saved_redirect` = :redir WHERE `id` = :sessid;');
+      if (!$result->execute(array(':redir' => '', ':sessid' => $session['id']))) {
+        $this->log('redir_save_failure', 'session: '.$session['id'].', redirect: (empty)');
+      }
+      else {
+        $success = true;
+      }
+    }
+    return $success;
+  }
+
   function getDomainBaseURL() {
     return ($this->running_on_localhost?'http':'https').'://'.$_SERVER['SERVER_NAME'];
   }
@@ -253,7 +353,19 @@ class AuthUtils {
     }
   }
 
-  function appendLoginForm($dom_element, $session, $user) {
+  function getGroupedEmails($group_id, $exclude_email = '') {
+    $emails = array();
+    if (intval($group_id)) {
+      $result = $this->db->prepare('SELECT `email` FROM `auth_users` WHERE `group_id` = :groupid AND `status` = \'ok\' AND `email` != :excludemail ORDER BY `email` ASC;');
+      $result->execute(array(':groupid' => $group_id, ':excludemail' => $exclude_email));
+      foreach ($result->fetchAll(PDO::FETCH_ASSOC) as $row) {
+        $emails[] = $row['email'];
+      }
+    }
+    return $emails;
+  }
+
+  function appendLoginForm($dom_element, $session, $user, $addfields = array()) {
     $form = $dom_element->appendForm('./', 'POST', 'loginform');
     $form->setAttribute('id', 'loginform');
     $form->setAttribute('class', 'loginarea hidden');
@@ -280,6 +392,9 @@ class AuthUtils {
     $label->setAttribute('class', 'loginprompt');
     $litem = $ulist->appendElement('li');
     $litem->appendInputHidden('tcode', $this->createTimeCode($session));
+    foreach ($addfields as $fname => $fvalue) {
+      $litem->appendInputHidden($fname, $fvalue);
+    }
     $submit = $litem->appendInputSubmit(_('Log in / Register'));
     $submit->setAttribute('class', 'loginbutton');
   }
index 23a045d66bc716e8589252f8014171d59330390a..5f091a26ae14b152d49f79e44f3f0ca145b1ea83 100644 (file)
--- a/index.php
+++ b/index.php
@@ -49,9 +49,11 @@ if (!count($errors)) {
       $errors[] = _('The email address is invalid.');
     }
     elseif ($utils->verifyTimeCode(@$_POST['tcode'], $session)) {
-      $result = $db->prepare('SELECT `id`, `pwdhash`, `email`, `status`, `verify_hash` FROM `auth_users` WHERE `email` = :email;');
+      $result = $db->prepare('SELECT `id`, `pwdhash`, `email`, `status`, `verify_hash`,`group_id` FROM `auth_users` WHERE `email` = :email;');
       $result->execute(array(':email' => $_POST['email']));
       $user = $result->fetch(PDO::FETCH_ASSOC);
+      // If we need to add the email to a group, note here which user's group we should be added to - otherwise, set to 0.
+      $addgroup = (array_key_exists('grouptoexisting', $_POST) && intval($session['user']) && ($session['user'] != @$user['id'])) ? $session['user'] : 0;
       if ($user['id'] && array_key_exists('pwd', $_POST)) {
         // existing user, check password
         if (($user['status'] == 'ok') && $utils->pwdVerify(@$_POST['pwd'], $user)) {
@@ -71,49 +73,8 @@ if (!count($errors)) {
 
           // Log user in - update session key for that, see https://wiki.mozilla.org/WebAppSec/Secure_Coding_Guidelines#Login
           $utils->log('login', 'user: '.$user['id']);
-          $sesskey = $utils->createSessionKey();
-          setcookie('sessionkey', $sesskey, 0, "", "", !$utils->running_on_localhost, true); // Last two params are secure and httponly, secure is not set on localhost.
-          // If the session has a redirect set, make sure it's performed.
-          if (strlen(@$session['saved_redirect'])) {
-            header('Location: '.$utils->getDomainBaseURL().$session['saved_redirect']);
-            // Remove redirect.
-            $result = $db->prepare('UPDATE `auth_sessions` SET `saved_redirect` = :redir WHERE `id` = :sessid;');
-            if (!$result->execute(array(':redir' => '', ':sessid' => $session['id']))) {
-              $utils->log('redir_save_failure', 'session: '.$session['id'].', redirect: (empty)');
-            }
-          }
-          // If the session has a user set, create a new one - otherwise take existing session entry.
-          if (intval($session['user'])) {
-            $result = $db->prepare('INSERT INTO `auth_sessions` (`sesskey`, `time_expire`, `user`, `logged_in`) VALUES (:sesskey, :expire, :userid, TRUE);');
-            $result->execute(array(':sesskey' => $sesskey, ':userid' => $user['id'], ':expire' => gmdate('Y-m-d H:i:s', strtotime('+1 day'))));
-            // After insert, actually fetch the session row from the DB so we have all values.
-            $result = $db->prepare('SELECT * FROM auth_sessions WHERE `sesskey` = :sesskey AND `time_expire` > :expire;');
-            $result->execute(array(':sesskey' => $sesskey, ':expire' => gmdate('Y-m-d H:i:s')));
-            $row = $result->fetch(PDO::FETCH_ASSOC);
-            if ($row) {
-              $session = $row;
-            }
-            else {
-              $utils->log('create_session_failure', 'at login, prev session: '.$session['id'].', new user: '.$user['id']);
-              $errors[] = _('The session system is not working. Please <a href="https://www.kairo.at/contact">contact KaiRo.at</a> and tell the team about this.');
-            }
-          }
-          else {
-            $result = $db->prepare('UPDATE `auth_sessions` SET `sesskey` = :sesskey, `user` = :userid, `logged_in` = TRUE, `time_expire` = :expire WHERE `id` = :sessid;');
-            if (!$result->execute(array(':sesskey' => $sesskey, ':userid' => $user['id'], ':expire' => gmdate('Y-m-d H:i:s', strtotime('+1 day')), ':sessid' => $session['id']))) {
-              $utils->log('login_failure', 'session: '.$session['id'].', user: '.$user['id']);
-              $errors[] = _('Login failed unexpectedly. Please <a href="https://www.kairo.at/contact">contact KaiRo.at</a> and tell the team about this.');
-            }
-            else {
-              // After update, actually fetch the session row from the DB so we have all values.
-              $result = $db->prepare('SELECT * FROM auth_sessions WHERE `sesskey` = :sesskey AND `time_expire` > :expire;');
-              $result->execute(array(':sesskey' => $sesskey, ':expire' => gmdate('Y-m-d H:i:s')));
-              $row = $result->fetch(PDO::FETCH_ASSOC);
-              if ($row) {
-                $session = $row;
-              }
-            }
-          }
+          $prev_session = $session;
+          $session = $utils->getLoginSession($user['id'], $session);
           // If a verify_hash if set on a verified user, a password reset had been requested. As a login works right now, cancel that reset request by deleting the hash.
           if (strlen(@$user['verify_hash'])) {
             $result = $db->prepare('UPDATE `auth_users` SET `verify_hash` = \'\' WHERE `id` = :userid;');
@@ -124,6 +85,7 @@ if (!count($errors)) {
               $user['verify_hash'] = '';
             }
           }
+          $utils->doRedirectIfSet($prev_session);
         }
         else {
           $errors[] = _('This password is invalid or your email is not verified yet. Did you type them correctly?');
@@ -141,7 +103,7 @@ if (!count($errors)) {
             $vcode = $utils->createVerificationCode();
             $result = $db->prepare('INSERT INTO `auth_users` (`email`, `pwdhash`, `status`, `verify_hash`) VALUES (:email, :pwdhash, \'unverified\', :vcode);');
             if (!$result->execute(array(':email' => $_POST['email'], ':pwdhash' => $newHash, ':vcode' => $vcode))) {
-              $utils->log('user_insert_failure', 'email: '.$_POST['email']);
+              $utils->log('user_insert_failure', 'email: '.$_POST['email'].' - '.$result->errorInfo()[2]);
               $errors[] = _('Could not add user. Please <a href="https://www.kairo.at/contact">contact KaiRo.at</a> and tell the team about this.');
             }
             $user = array('id' => $db->lastInsertId(),
@@ -218,6 +180,31 @@ if (!count($errors)) {
           }
         }
       }
+      if (!count($errors) && ($addgroup > 0)) {
+        // We should add the login email to the group of that existing user.
+        $result = $db->prepare('SELECT `group_id` FROM `auth_users` WHERE `id` = :userid;');
+        $result->execute(array(':userid' => $addgroup));
+        $grpuser = $result->fetch(PDO::FETCH_ASSOC);
+        if (!intval($grpuser['group_id'])) {
+          // If that user doesn't have a group, put him into a group with his own user ID.
+          $result = $db->prepare('UPDATE `auth_users` SET `group_id` = :groupid WHERE `id` = :userid;');
+          if (!$result->execute(array(':groupid' => $addgroup, ':userid' => $addgroup))) {
+            $utils->log('group_save_failure', 'user: '.$addgroup);
+          }
+          else {
+            $utils->log('new grouping', 'user: '.$addgroup.', group: '.$addgroup);
+          }
+        }
+        // Save grouping for the new or logged-in user.
+        $result = $db->prepare('UPDATE `auth_users` SET `group_id` = :groupid WHERE `id` = :userid;');
+        if (!$result->execute(array(':groupid' => $addgroup, ':userid' => $user['id']))) {
+          $utils->log('group_save_failure', 'user: '.$user['id']);
+        }
+        else {
+          $utils->log('new grouping', 'user: '.$user['id'].', group: '.$addgroup);
+          $user['group_id'] = $addgroup;
+        }
+      }
     }
     else {
       $errors[] = _('The form you used was not valid. Possibly it has expired and you need to initiate the action again, or you have disabled cookies for this site.');
@@ -324,7 +311,7 @@ if (!count($errors)) {
     }
   }
   elseif (intval($session['user'])) {
-    $result = $db->prepare('SELECT `id`,`email`,`verify_hash` FROM `auth_users` WHERE `id` = :userid;');
+    $result = $db->prepare('SELECT `id`,`email`,`verify_hash`,`group_id` FROM `auth_users` WHERE `id` = :userid;');
     $result->execute(array(':userid' => $session['user']));
     $user = $result->fetch(PDO::FETCH_ASSOC);
     if (!$user['id']) {
@@ -357,6 +344,9 @@ if (!count($errors)) {
         }
       }
     }
+    else {
+      $utils->doRedirectIfSet($session);
+    }
   }
 }
 
@@ -469,19 +459,26 @@ if (!count($errors)) {
     $para->setAttribute('class', 'toplink');
     $link = $para->appendLink('./', _('Back to top'));
   }
-  elseif ($session['logged_in']) {
+  elseif ($session['logged_in'] && (!array_key_exists('addemail', $_GET))) {
     if ($pagetype == 'reset_done') {
       $para = $body->appendElement('p', _('Your password has successfully been reset.'));
       $para->setAttribute('class', 'resetinfo done');
     }
     $div = $body->appendElement('div', $user['email']);
     $div->setAttribute('class', 'loginheader');
+    $groupmails = $utils->getGroupedEmails($user['group_id'], $user['email']);
+    if (count($groupmails)) {
+      $para = $div->appendElement('p', _('Grouped with: ').implode(', ', $groupmails));
+      $para->setAttribute('class', 'small groupmails');
+    }
     $div = $body->appendElement('div');
     $div->setAttribute('class', 'loginlinks');
     $ulist = $div->appendElement('ul');
     $ulist->setAttribute('class', 'flat');
     $litem = $ulist->appendElement('li');
     $link = $litem->appendLink('./?logout', _('Log out'));
+    $litem = $ulist->appendElement('li');
+    $link = $litem->appendLink('./?addemail', _('Add another email address'));
     if (in_array($user['email'], $utils->client_reg_email_whitelist)) {
       $litem = $ulist->appendElement('li');
       $link = $litem->appendLink('./?clients', _('Manage OAuth2 clients'));
@@ -490,6 +487,7 @@ if (!count($errors)) {
     $litem->appendLink('./?reset', _('Set new password'));
   }
   else { // not logged in
+    $addfields = array();
     if ($pagetype == 'verification_done') {
       $para = $body->appendElement('p', _('Hooray! Your email was successfully confirmed! You can log in now.'));
       $para->setAttribute('class', 'verifyinfo done');
@@ -498,7 +496,12 @@ if (!count($errors)) {
       $para = $body->appendElement('p', _('Your password has successfully been reset. You can log in now with the new password.'));
       $para->setAttribute('class', 'resetinfo done');
     }
-    $utils->appendLoginForm($body, $session, $user);
+    elseif (array_key_exists('addemail', $_GET)) {
+      $para = $body->appendElement('p', sprintf(_('Add another email grouped with %s by either logging in with it or specifying the email and a new password to use.'), $user['email']));
+      $para->setAttribute('class', 'addemailinfo');
+      $addfields['grouptoexisting'] = '1';
+    }
+    $utils->appendLoginForm($body, $session, $user, $addfields);
   }
 }