@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

When users have no password on their account, guide them through the "reset password" flow in the guise of "set password"

Summary:
Depends on D20119. Fixes T9512. When you don't have a password on your account, the "Password" panel in Settings is non-obviously useless: you can't provide an old password, so you can't change your password.

The correct remedy is to "Forgot password?" and go through the password reset flow. However, we don't guide you to this and it isn't really self-evident.

Instead:

- Guide users to the password reset flow.
- Make it work when you're already logged in.
- Skin it as a "set password" flow.

We're still requiring you to prove you own the email associated with your account. This is a pretty weak requirement, but maybe stops attackers who use the computer at the library after you do in some bizarre emergency and forget to log out? It would probably be fine to just let users "set password", this mostly just keeps us from having two different pieces of code responsible for setting passwords.

Test Plan:
- Set password as a logged-in user.
- Reset password on the normal flow as a logged-out user.

Reviewers: amckinley

Reviewed By: amckinley

Subscribers: revi

Maniphest Tasks: T9512

Differential Revision: https://secure.phabricator.com/D20120

+134 -45
+20 -6
src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php
··· 14 14 $key = $request->getURIData('key'); 15 15 $email_id = $request->getURIData('emailID'); 16 16 17 - if ($request->getUser()->isLoggedIn()) { 18 - return $this->renderError( 19 - pht('You are already logged in.')); 20 - } 21 - 22 17 $target_user = id(new PhabricatorPeopleQuery()) 23 18 ->setViewer(PhabricatorUser::getOmnipotentUser()) 24 19 ->withIDs(array($id)) 25 20 ->executeOne(); 26 21 if (!$target_user) { 27 22 return new Aphront404Response(); 23 + } 24 + 25 + // NOTE: We allow you to use a one-time login link for your own current 26 + // login account. This supports the "Set Password" flow. 27 + 28 + $is_logged_in = false; 29 + if ($viewer->isLoggedIn()) { 30 + if ($viewer->getPHID() !== $target_user->getPHID()) { 31 + return $this->renderError( 32 + pht('You are already logged in.')); 33 + } else { 34 + $is_logged_in = true; 35 + } 28 36 } 29 37 30 38 // NOTE: As a convenience to users, these one-time login URIs may also ··· 100 108 ->addCancelButton('/'); 101 109 } 102 110 103 - if ($request->isFormPost()) { 111 + if ($request->isFormPost() || $is_logged_in) { 104 112 // If we have an email bound into this URI, verify email so that clicking 105 113 // the link in the "Welcome" email is good enough, without requiring users 106 114 // to go through a second round of email verification. ··· 120 128 unset($unguarded); 121 129 122 130 $next_uri = $this->getNextStepURI($target_user); 131 + 132 + // If the user is already logged in, we're just doing a "password set" 133 + // flow. Skip directly to the next step. 134 + if ($is_logged_in) { 135 + return id(new AphrontRedirectResponse())->setURI($next_uri); 136 + } 123 137 124 138 PhabricatorCookies::setNextURICookie($request, $next_uri, $force = true); 125 139
+80 -27
src/applications/auth/controller/PhabricatorEmailLoginController.php
··· 9 9 10 10 public function handleRequest(AphrontRequest $request) { 11 11 $viewer = $this->getViewer(); 12 + $is_logged_in = $viewer->isLoggedIn(); 12 13 13 14 $e_email = true; 14 15 $e_captcha = true; 15 16 $errors = array(); 16 17 17 - $v_email = $request->getStr('email'); 18 + if ($is_logged_in) { 19 + if (!$this->isPasswordAuthEnabled()) { 20 + return $this->newDialog() 21 + ->setTitle(pht('No Password Auth')) 22 + ->appendParagraph( 23 + pht( 24 + 'Password authentication is not enabled and you are already '. 25 + 'logged in. There is nothing for you here.')) 26 + ->addCancelButton('/', pht('Continue')); 27 + } 28 + 29 + $v_email = $viewer->loadPrimaryEmailAddress(); 30 + } else { 31 + $v_email = $request->getStr('email'); 32 + } 33 + 18 34 if ($request->isFormPost()) { 19 35 $e_email = null; 20 36 $e_captcha = pht('Again'); 21 37 22 - $captcha_ok = AphrontFormRecaptchaControl::processCaptcha($request); 23 - if (!$captcha_ok) { 24 - $errors[] = pht('Captcha response is incorrect, try again.'); 25 - $e_captcha = pht('Invalid'); 38 + if (!$is_logged_in) { 39 + $captcha_ok = AphrontFormRecaptchaControl::processCaptcha($request); 40 + if (!$captcha_ok) { 41 + $errors[] = pht('Captcha response is incorrect, try again.'); 42 + $e_captcha = pht('Invalid'); 43 + } 26 44 } 27 45 28 46 if (!strlen($v_email)) { ··· 76 94 } 77 95 78 96 if (!$errors) { 79 - $body = $this->newAccountLoginMailBody($target_user); 97 + $body = $this->newAccountLoginMailBody( 98 + $target_user, 99 + $is_logged_in); 100 + 101 + if ($is_logged_in) { 102 + $subject = pht('[Phabricator] Account Password Link'); 103 + $instructions = pht( 104 + 'An email has been sent containing a link you can use to set '. 105 + 'a password for your account.'); 106 + } else { 107 + $subject = pht('[Phabricator] Account Login Link'); 108 + $instructions = pht( 109 + 'An email has been sent containing a link you can use to log '. 110 + 'in to your account.'); 111 + } 80 112 81 113 $mail = id(new PhabricatorMetaMTAMail()) 82 - ->setSubject(pht('[Phabricator] Account Login Link')) 114 + ->setSubject($subject) 83 115 ->setForceDelivery(true) 84 116 ->addRawTos(array($target_email->getAddress())) 85 117 ->setBody($body) ··· 88 120 return $this->newDialog() 89 121 ->setTitle(pht('Check Your Email')) 90 122 ->setShortTitle(pht('Email Sent')) 91 - ->appendParagraph( 92 - pht('An email has been sent with a link you can use to log in.')) 123 + ->appendParagraph($instructions) 93 124 ->addCancelButton('/', pht('Done')); 94 125 } 95 126 } ··· 99 130 ->setViewer($viewer); 100 131 101 132 if ($this->isPasswordAuthEnabled()) { 102 - $form->appendRemarkupInstructions( 103 - pht( 104 - 'To reset your password, provide your email address. An email '. 105 - 'with a login link will be sent to you.')); 133 + if ($is_logged_in) { 134 + $title = pht('Set Password'); 135 + $form->appendRemarkupInstructions( 136 + pht( 137 + 'A password reset link will be sent to your primary email '. 138 + 'address. Follow the link to set an account password.')); 139 + } else { 140 + $title = pht('Password Reset'); 141 + $form->appendRemarkupInstructions( 142 + pht( 143 + 'To reset your password, provide your email address. An email '. 144 + 'with a login link will be sent to you.')); 145 + } 106 146 } else { 147 + $title = pht('Email Login'); 107 148 $form->appendRemarkupInstructions( 108 149 pht( 109 150 'To access your account, provide your email address. An email '. 110 151 'with a login link will be sent to you.')); 111 152 } 112 153 154 + if ($is_logged_in) { 155 + $address_control = new AphrontFormStaticControl(); 156 + } else { 157 + $address_control = id(new AphrontFormTextControl()) 158 + ->setName('email') 159 + ->setError($e_email); 160 + } 161 + 162 + $address_control 163 + ->setLabel(pht('Email Address')) 164 + ->setValue($v_email); 165 + 113 166 $form 114 - ->appendControl( 115 - id(new AphrontFormTextControl()) 116 - ->setLabel(pht('Email Address')) 117 - ->setName('email') 118 - ->setValue($v_email) 119 - ->setError($e_email)) 120 - ->appendControl( 167 + ->appendControl($address_control); 168 + 169 + if (!$is_logged_in) { 170 + $form->appendControl( 121 171 id(new AphrontFormRecaptchaControl()) 122 172 ->setLabel(pht('Captcha')) 123 173 ->setError($e_captcha)); 124 - 125 - if ($this->isPasswordAuthEnabled()) { 126 - $title = pht('Password Reset'); 127 - } else { 128 - $title = pht('Email Login'); 129 174 } 130 175 131 176 return $this->newDialog() ··· 137 182 ->addSubmitButton(pht('Send Email')); 138 183 } 139 184 140 - private function newAccountLoginMailBody(PhabricatorUser $user) { 185 + private function newAccountLoginMailBody( 186 + PhabricatorUser $user, 187 + $is_logged_in) { 188 + 141 189 $engine = new PhabricatorAuthSessionEngine(); 142 190 $uri = $engine->getOneTimeLoginURI( 143 191 $user, ··· 148 196 $have_passwords = $this->isPasswordAuthEnabled(); 149 197 150 198 if ($have_passwords) { 151 - if ($is_serious) { 199 + if ($is_logged_in) { 200 + $body = pht( 201 + 'You can use this link to set a password on your account:'. 202 + "\n\n %s\n", 203 + $uri); 204 + } else if ($is_serious) { 152 205 $body = pht( 153 206 "You can use this link to reset your Phabricator password:". 154 207 "\n\n %s\n",
+34 -12
src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php
··· 34 34 35 35 $content_source = PhabricatorContentSource::newFromRequest($request); 36 36 37 - $token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession( 38 - $viewer, 39 - $request, 40 - '/settings/'); 41 - 42 37 $min_len = PhabricatorEnv::getEnvConfig('account.minimum-password-length'); 43 38 $min_len = (int)$min_len; 44 39 ··· 55 50 ->withPasswordTypes(array($account_type)) 56 51 ->withIsRevoked(false) 57 52 ->execute(); 58 - if ($password_objects) { 59 - $password_object = head($password_objects); 60 - } else { 61 - $password_object = PhabricatorAuthPassword::initializeNewPassword( 62 - $user, 63 - $account_type); 53 + if (!$password_objects) { 54 + return $this->newSetPasswordView($request); 64 55 } 56 + $password_object = head($password_objects); 65 57 66 58 $e_old = true; 67 59 $e_new = true; 68 60 $e_conf = true; 69 61 70 62 $errors = array(); 71 - if ($request->isFormPost()) { 63 + if ($request->isFormOrHisecPost()) { 64 + $workflow_key = sprintf( 65 + 'password.change(%s)', 66 + $user->getPHID()); 67 + 68 + $hisec_token = id(new PhabricatorAuthSessionEngine()) 69 + ->setWorkflowKey($workflow_key) 70 + ->requireHighSecurityToken($viewer, $request, '/settings/'); 71 + 72 72 // Rate limit guesses about the old password. This page requires MFA and 73 73 // session compromise already, so this is mostly just to stop researchers 74 74 // from reporting this as a vulnerability. ··· 216 216 $algo_box, 217 217 $info_view, 218 218 ); 219 + } 220 + 221 + private function newSetPasswordView(AphrontRequest $request) { 222 + $viewer = $request->getUser(); 223 + $user = $this->getUser(); 224 + 225 + $form = id(new AphrontFormView()) 226 + ->setViewer($viewer) 227 + ->appendRemarkupInstructions( 228 + pht( 229 + 'Your account does not currently have a password set. You can '. 230 + 'choose a password by performing a password reset.')) 231 + ->appendControl( 232 + id(new AphrontFormSubmitControl()) 233 + ->addCancelButton('/login/email/', pht('Reset Password'))); 234 + 235 + $form_box = id(new PHUIObjectBoxView()) 236 + ->setHeaderText(pht('Set Password')) 237 + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) 238 + ->setForm($form); 239 + 240 + return $form_box; 219 241 } 220 242 221 243