diff --git a/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php b/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php
index 12decda80d..a40ab02d89 100644
--- a/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php
+++ b/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php
@@ -1,190 +1,199 @@
 <?php
 
 final class PhabricatorAuthOneTimeLoginController
   extends PhabricatorAuthController {
 
   private $id;
   private $key;
   private $emailID;
   private $linkType;
 
   public function shouldRequireLogin() {
     return false;
   }
 
   public function willProcessRequest(array $data) {
     $this->linkType = $data['type'];
     $this->id = $data['id'];
     $this->key = $data['key'];
     $this->emailID = idx($data, 'emailID');
   }
 
   public function processRequest() {
     $request = $this->getRequest();
 
     if ($request->getUser()->isLoggedIn()) {
       return $this->renderError(
         pht('You are already logged in.'));
     }
 
     $target_user = id(new PhabricatorPeopleQuery())
       ->setViewer(PhabricatorUser::getOmnipotentUser())
       ->withIDs(array($this->id))
       ->executeOne();
     if (!$target_user) {
       return new Aphront404Response();
     }
 
     // NOTE: As a convenience to users, these one-time login URIs may also
     // be associated with an email address which will be verified when the
     // URI is used.
 
     // This improves the new user experience for users receiving "Welcome"
     // emails on installs that require verification: if we did not verify the
     // email, they'd immediately get roadblocked with a "Verify Your Email"
     // error and have to go back to their email account, wait for a
     // "Verification" email, and then click that link to actually get access to
     // their account. This is hugely unwieldy, and if the link was only sent
     // to the user's email in the first place we can safely verify it as a
     // side effect of login.
 
     // The email hashed into the URI so users can't verify some email they
     // do not own by doing this:
     //
     //  - Add some address you do not own;
     //  - request a password reset;
     //  - change the URI in the email to the address you don't own;
     //  - login via the email link; and
     //  - get a "verified" address you don't control.
 
     $target_email = null;
     if ($this->emailID) {
       $target_email = id(new PhabricatorUserEmail())->loadOneWhere(
         'userPHID = %s AND id = %d',
         $target_user->getPHID(),
         $this->emailID);
       if (!$target_email) {
         return new Aphront404Response();
       }
     }
 
     $engine = new PhabricatorAuthSessionEngine();
     $token = $engine->loadOneTimeLoginKey(
       $target_user,
       $target_email,
       $this->key);
 
     if (!$token) {
       return $this->newDialog()
         ->setTitle(pht('Unable to Login'))
         ->setShortTitle(pht('Login Failure'))
         ->appendParagraph(
           pht(
             'The login link you clicked is invalid, out of date, or has '.
             'already been used.'))
         ->appendParagraph(
           pht(
             'Make sure you are copy-and-pasting the entire link into '.
             'your browser. Login links are only valid for 24 hours, and '.
             'can only be used once.'))
         ->appendParagraph(
           pht('You can try again, or request a new link via email.'))
         ->addCancelButton('/login/email/', pht('Send Another Email'));
     }
 
     if ($request->isFormPost()) {
       // If we have an email bound into this URI, verify email so that clicking
       // the link in the "Welcome" email is good enough, without requiring users
       // to go through a second round of email verification.
 
       $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
-        // Nuke the token so that this URI is one-time only.
-        $token->delete();
+        // Nuke the token and all other outstanding password reset tokens.
+        // There is no particular security benefit to destroying them all, but
+        // it should reduce HackerOne reports of nebulous harm.
+
+        PhabricatorAuthTemporaryToken::revokeTokens(
+          $target_user,
+          array($target_user->getPHID()),
+          array(
+            PhabricatorAuthSessionEngine::ONETIME_TEMPORARY_TOKEN_TYPE,
+            PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE,
+          ));
 
         if ($target_email) {
           id(new PhabricatorUserEditor())
             ->setActor($target_user)
             ->verifyEmail($target_user, $target_email);
         }
       unset($unguarded);
 
       $next = '/';
       if (!PhabricatorPasswordAuthProvider::getPasswordProvider()) {
         $next = '/settings/panel/external/';
       } else if (PhabricatorEnv::getEnvConfig('account.editable')) {
 
         // We're going to let the user reset their password without knowing
         // the old one. Generate a one-time token for that.
         $key = Filesystem::readRandomCharacters(16);
 
         $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
           id(new PhabricatorAuthTemporaryToken())
             ->setObjectPHID($target_user->getPHID())
             ->setTokenType(
               PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE)
             ->setTokenExpires(time() + phutil_units('1 hour in seconds'))
             ->setTokenCode(PhabricatorHash::digest($key))
             ->save();
         unset($unguarded);
 
         $next = (string)id(new PhutilURI('/settings/panel/password/'))
           ->setQueryParams(
             array(
               'key' => $key,
             ));
 
         $request->setTemporaryCookie(PhabricatorCookies::COOKIE_HISEC, 'yes');
       }
 
       PhabricatorCookies::setNextURICookie($request, $next, $force = true);
 
       return $this->loginUser($target_user);
     }
 
     // NOTE: We need to CSRF here so attackers can't generate an email link,
     // then log a user in to an account they control via sneaky invisible
     // form submissions.
 
     switch ($this->linkType) {
       case PhabricatorAuthSessionEngine::ONETIME_WELCOME:
         $title = pht('Welcome to Phabricator');
         break;
       case PhabricatorAuthSessionEngine::ONETIME_RECOVER:
         $title = pht('Account Recovery');
         break;
       case PhabricatorAuthSessionEngine::ONETIME_USERNAME:
       case PhabricatorAuthSessionEngine::ONETIME_RESET:
       default:
         $title = pht('Login to Phabricator');
         break;
     }
 
     $body = array();
     $body[] = pht(
       'Use the button below to log in as: %s',
       phutil_tag('strong', array(), $target_user->getUsername()));
 
     if ($target_email && !$target_email->getIsVerified()) {
       $body[] = pht(
         'Logging in will verify %s as an email address you own.',
         phutil_tag('strong', array(), $target_email->getAddress()));
 
     }
 
     $body[] = pht(
       'After logging in you should set a password for your account, or '.
       'link your account to an external account that you can use to '.
       'authenticate in the future.');
 
     $dialog = $this->newDialog()
       ->setTitle($title)
       ->addSubmitButton(pht('Login (%s)', $target_user->getUsername()))
       ->addCancelButton('/');
 
     foreach ($body as $paragraph) {
       $dialog->appendParagraph($paragraph);
     }
 
     return id(new AphrontDialogResponse())->setDialog($dialog);
   }
 }
diff --git a/src/applications/auth/controller/PhabricatorEmailLoginController.php b/src/applications/auth/controller/PhabricatorEmailLoginController.php
index 7ec9ae97b1..8b5a6b19c5 100644
--- a/src/applications/auth/controller/PhabricatorEmailLoginController.php
+++ b/src/applications/auth/controller/PhabricatorEmailLoginController.php
@@ -1,155 +1,175 @@
 <?php
 
 final class PhabricatorEmailLoginController
   extends PhabricatorAuthController {
 
   public function shouldRequireLogin() {
     return false;
   }
 
   public function processRequest() {
     $request = $this->getRequest();
 
     if (!PhabricatorPasswordAuthProvider::getPasswordProvider()) {
       return new Aphront400Response();
     }
 
     $e_email = true;
     $e_captcha = true;
     $errors = array();
 
     $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
 
     if ($request->isFormPost()) {
       $e_email = null;
       $e_captcha = pht('Again');
 
       $captcha_ok = AphrontFormRecaptchaControl::processCaptcha($request);
       if (!$captcha_ok) {
         $errors[] = pht('Captcha response is incorrect, try again.');
         $e_captcha = pht('Invalid');
       }
 
       $email = $request->getStr('email');
       if (!strlen($email)) {
        $errors[] = pht('You must provide an email address.');
        $e_email = pht('Required');
       }
 
       if (!$errors) {
         // NOTE: Don't validate the email unless the captcha is good; this makes
         // it expensive to fish for valid email addresses while giving the user
         // a better error if they goof their email.
 
         $target_email = id(new PhabricatorUserEmail())->loadOneWhere(
           'address = %s',
           $email);
 
         $target_user = null;
         if ($target_email) {
           $target_user = id(new PhabricatorUser())->loadOneWhere(
             'phid = %s',
             $target_email->getUserPHID());
         }
 
         if (!$target_user) {
           $errors[] =
             pht('There is no account associated with that email address.');
           $e_email = pht('Invalid');
         }
 
+        // If this address is unverified, only send a reset link to it if
+        // the account has no verified addresses. This prevents an opportunistic
+        // attacker from compromising an account if a user adds an email
+        // address but mistypes it and doesn't notice.
+
+        // (For a newly created account, all the addresses may be unverified,
+        // which is why we'll send to an unverified address in that case.)
+
+        if ($target_email && !$target_email->getIsVerified()) {
+          $verified_addresses = id(new PhabricatorUserEmail())->loadAllWhere(
+            'userPHID = %s AND isVerified = 1',
+            $target_email->getUserPHID());
+          if ($verified_addresses) {
+            $errors[] = pht(
+              'That email addess is not verified. You can only send '.
+              'password reset links to a verified address.');
+            $e_email = pht('Unverified');
+          }
+        }
+
         if (!$errors) {
           $engine = new PhabricatorAuthSessionEngine();
           $uri = $engine->getOneTimeLoginURI(
             $target_user,
             null,
             PhabricatorAuthSessionEngine::ONETIME_RESET);
 
           if ($is_serious) {
             $body = <<<EOBODY
 You can use this link to reset your Phabricator password:
 
   {$uri}
 
 EOBODY;
           } else {
             $body = <<<EOBODY
 Condolences on forgetting your password. You can use this link to reset it:
 
   {$uri}
 
 After you set a new password, consider writing it down on a sticky note and
 attaching it to your monitor so you don't forget again! Choosing a very short,
 easy-to-remember password like "cat" or "1234" might also help.
 
 Best Wishes,
 Phabricator
 
 EOBODY;
           }
 
           // NOTE: Don't set the user as 'from', or they may not receive the
           // mail if they have the "don't send me email about my own actions"
           // preference set.
 
           $mail = id(new PhabricatorMetaMTAMail())
             ->setSubject(pht('[Phabricator] Password Reset'))
             ->addRawTos(array($target_email->getAddress()))
             ->setBody($body)
             ->saveAndSend();
 
           return $this->newDialog()
             ->setTitle(pht('Check Your Email'))
             ->setShortTitle(pht('Email Sent'))
             ->appendParagraph(
               pht('An email has been sent with a link you can use to login.'))
             ->addCancelButton('/', pht('Done'));
         }
       }
 
     }
 
     $error_view = null;
     if ($errors) {
       $error_view = new AphrontErrorView();
       $error_view->setErrors($errors);
     }
 
     $email_auth = new PHUIFormLayoutView();
     $email_auth->appendChild($error_view);
     $email_auth
       ->setUser($request->getUser())
       ->setFullWidth(true)
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('Email'))
           ->setName('email')
           ->setValue($request->getStr('email'))
           ->setError($e_email))
       ->appendChild(
         id(new AphrontFormRecaptchaControl())
           ->setLabel(pht('Captcha'))
           ->setError($e_captcha));
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Reset Password'));
 
     $dialog = new AphrontDialogView();
     $dialog->setUser($request->getUser());
     $dialog->setTitle(pht(
       'Forgot Password / Email Login'));
     $dialog->appendChild($email_auth);
     $dialog->addSubmitButton(pht('Send Email'));
     $dialog->setSubmitURI('/login/email/');
 
     return $this->buildApplicationPage(
       array(
         $crumbs,
         $dialog,
       ),
       array(
         'title' => pht('Forgot Password'),
       ));
   }
 
 }