@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 follow an email login link but an install does not use passwords, try to get them to link an account

Summary:
Ref T13249. See PHI774. When users follow an email login link ("Forgot password?", "Send Welcome Email", "Send a login link to your email address.", `bin/auth recover`), we send them to a password reset flow if an install uses passwords.

If an install does not use passwords, we previously dumped them unceremoniously into the {nav Settings > External Accounts} UI with no real guidance about what they were supposed to do. Since D20094 we do a slightly better job here in some cases. Continue improving this workflow.

This adds a page like "Reset Password" for "Hey, You Should Probably Link An Account, Here's Some Options".

Overall, this stuff is still pretty rough in a couple of areas that I imagine addressing in the future:

- When you finish linking, we still dump you back in Settings. At least we got you to link things. But better would be to return you here and say "great job, you're a pro".
- This UI can become a weird pile of buttons in certain configs and generally looks a little unintentional. This problem is shared among all the "linkable" providers, and the non-login link flow is also weird.

So: step forward, but more work to be done.

Test Plan: {F6211115}

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13249

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

+174 -8
+3 -3
resources/celerity/map.php
··· 9 9 'names' => array( 10 10 'conpherence.pkg.css' => '3c8a0668', 11 11 'conpherence.pkg.js' => '020aebcf', 12 - 'core.pkg.css' => 'e0f5d66f', 12 + 'core.pkg.css' => '4ed8ce1f', 13 13 'core.pkg.js' => '5c737607', 14 14 'differential.pkg.css' => 'b8df73d4', 15 15 'differential.pkg.js' => '67c9ea4c', ··· 164 164 'rsrc/css/phui/phui-left-right.css' => '68513c34', 165 165 'rsrc/css/phui/phui-lightbox.css' => '4ebf22da', 166 166 'rsrc/css/phui/phui-list.css' => '470b1adb', 167 - 'rsrc/css/phui/phui-object-box.css' => '9b58483d', 167 + 'rsrc/css/phui/phui-object-box.css' => 'f434b6be', 168 168 'rsrc/css/phui/phui-pager.css' => 'd022c7ad', 169 169 'rsrc/css/phui/phui-pinboard-view.css' => '1f08f5d8', 170 170 'rsrc/css/phui/phui-property-list-view.css' => 'cad62236', ··· 833 833 'phui-left-right-css' => '68513c34', 834 834 'phui-lightbox-css' => '4ebf22da', 835 835 'phui-list-view-css' => '470b1adb', 836 - 'phui-object-box-css' => '9b58483d', 836 + 'phui-object-box-css' => 'f434b6be', 837 837 'phui-oi-big-ui-css' => '9e037c7a', 838 838 'phui-oi-color-css' => 'b517bfa0', 839 839 'phui-oi-drag-ui-css' => 'da15d3dc',
+4
src/__phutil_library_map__.php
··· 2272 2272 'PhabricatorAuthInviteVerifyException' => 'applications/auth/exception/PhabricatorAuthInviteVerifyException.php', 2273 2273 'PhabricatorAuthInviteWorker' => 'applications/auth/worker/PhabricatorAuthInviteWorker.php', 2274 2274 'PhabricatorAuthLinkController' => 'applications/auth/controller/PhabricatorAuthLinkController.php', 2275 + 'PhabricatorAuthLinkMessageType' => 'applications/auth/message/PhabricatorAuthLinkMessageType.php', 2275 2276 'PhabricatorAuthListController' => 'applications/auth/controller/config/PhabricatorAuthListController.php', 2276 2277 'PhabricatorAuthLoginController' => 'applications/auth/controller/PhabricatorAuthLoginController.php', 2277 2278 'PhabricatorAuthLoginMessageType' => 'applications/auth/message/PhabricatorAuthLoginMessageType.php', ··· 2370 2371 'PhabricatorAuthSessionPHIDType' => 'applications/auth/phid/PhabricatorAuthSessionPHIDType.php', 2371 2372 'PhabricatorAuthSessionQuery' => 'applications/auth/query/PhabricatorAuthSessionQuery.php', 2372 2373 'PhabricatorAuthSessionRevoker' => 'applications/auth/revoker/PhabricatorAuthSessionRevoker.php', 2374 + 'PhabricatorAuthSetExternalController' => 'applications/auth/controller/PhabricatorAuthSetExternalController.php', 2373 2375 'PhabricatorAuthSetPasswordController' => 'applications/auth/controller/PhabricatorAuthSetPasswordController.php', 2374 2376 'PhabricatorAuthSetupCheck' => 'applications/config/check/PhabricatorAuthSetupCheck.php', 2375 2377 'PhabricatorAuthStartController' => 'applications/auth/controller/PhabricatorAuthStartController.php', ··· 8023 8025 'PhabricatorAuthInviteVerifyException' => 'PhabricatorAuthInviteDialogException', 8024 8026 'PhabricatorAuthInviteWorker' => 'PhabricatorWorker', 8025 8027 'PhabricatorAuthLinkController' => 'PhabricatorAuthController', 8028 + 'PhabricatorAuthLinkMessageType' => 'PhabricatorAuthMessageType', 8026 8029 'PhabricatorAuthListController' => 'PhabricatorAuthProviderConfigController', 8027 8030 'PhabricatorAuthLoginController' => 'PhabricatorAuthController', 8028 8031 'PhabricatorAuthLoginMessageType' => 'PhabricatorAuthMessageType', ··· 8142 8145 'PhabricatorAuthSessionPHIDType' => 'PhabricatorPHIDType', 8143 8146 'PhabricatorAuthSessionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 8144 8147 'PhabricatorAuthSessionRevoker' => 'PhabricatorAuthRevoker', 8148 + 'PhabricatorAuthSetExternalController' => 'PhabricatorAuthController', 8145 8149 'PhabricatorAuthSetPasswordController' => 'PhabricatorAuthController', 8146 8150 'PhabricatorAuthSetupCheck' => 'PhabricatorSetupCheck', 8147 8151 'PhabricatorAuthStartController' => 'PhabricatorAuthController',
+2
src/applications/auth/application/PhabricatorAuthApplication.php
··· 86 86 => 'PhabricatorAuthSSHKeyRevokeController', 87 87 'view/(?P<id>\d+)/' => 'PhabricatorAuthSSHKeyViewController', 88 88 ), 89 + 89 90 'password/' => 'PhabricatorAuthSetPasswordController', 91 + 'external/' => 'PhabricatorAuthSetExternalController', 90 92 91 93 'mfa/' => array( 92 94 $this->getQueryRoutePattern() =>
+30 -2
src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php
··· 225 225 return (string)new PhutilURI($panel_uri, $params); 226 226 } 227 227 228 - $providers = id(new PhabricatorAuthProviderConfigQuery()) 228 + // Check if the user already has external accounts linked. If they do, 229 + // it's not obvious why they aren't using them to log in, but assume they 230 + // know what they're doing. We won't send them to the link workflow. 231 + $accounts = id(new PhabricatorExternalAccountQuery()) 232 + ->setViewer($user) 233 + ->withUserPHIDs(array($user->getPHID())) 234 + ->execute(); 235 + 236 + $configs = id(new PhabricatorAuthProviderConfigQuery()) 229 237 ->setViewer($user) 230 238 ->withIsEnabled(true) 231 239 ->execute(); 232 240 241 + $linkable = array(); 242 + foreach ($configs as $config) { 243 + if (!$config->getShouldAllowLink()) { 244 + continue; 245 + } 246 + 247 + $provider = $config->getProvider(); 248 + if (!$provider->isLoginFormAButton()) { 249 + continue; 250 + } 251 + 252 + $linkable[] = $provider; 253 + } 254 + 255 + // If there's at least one linkable provider, and the user doesn't already 256 + // have accounts, send the user to the link workflow. 257 + if (!$accounts && $linkable) { 258 + return '/auth/external/'; 259 + } 260 + 233 261 // If there are no configured providers and the user is an administrator, 234 262 // send them to Auth to configure a provider. This is probably where they 235 263 // want to go. You can end up in this state by accidentally losing your 236 264 // first session during initial setup, or after restoring exported data 237 265 // from a hosted instance. 238 - if (!$providers && $user->getIsAdmin()) { 266 + if (!$configs && $user->getIsAdmin()) { 239 267 return '/auth/'; 240 268 } 241 269
+110
src/applications/auth/controller/PhabricatorAuthSetExternalController.php
··· 1 + <?php 2 + 3 + final class PhabricatorAuthSetExternalController 4 + extends PhabricatorAuthController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $this->getViewer(); 8 + 9 + $configs = id(new PhabricatorAuthProviderConfigQuery()) 10 + ->setViewer($viewer) 11 + ->withIsEnabled(true) 12 + ->execute(); 13 + 14 + $linkable = array(); 15 + foreach ($configs as $config) { 16 + if (!$config->getShouldAllowLink()) { 17 + continue; 18 + } 19 + 20 + // For now, only buttons get to appear here: for example, we can't 21 + // reasonably embed an entire LDAP form into this UI. 22 + $provider = $config->getProvider(); 23 + if (!$provider->isLoginFormAButton()) { 24 + continue; 25 + } 26 + 27 + $linkable[] = $config; 28 + } 29 + 30 + if (!$linkable) { 31 + return $this->newDialog() 32 + ->setTitle(pht('No Linkable External Providers')) 33 + ->appendParagraph( 34 + pht( 35 + 'Currently, there are no configured external auth providers '. 36 + 'which you can link your account to.')) 37 + ->addCancelButton('/'); 38 + } 39 + 40 + $text = PhabricatorAuthMessage::loadMessageText( 41 + $viewer, 42 + PhabricatorAuthLinkMessageType::MESSAGEKEY); 43 + if (!strlen($text)) { 44 + $text = pht( 45 + 'You can link your Phabricator account to an external account to '. 46 + 'allow you to log in more easily in the future. To continue, choose '. 47 + 'an account to link below. If you prefer not to link your account, '. 48 + 'you can skip this step.'); 49 + } 50 + 51 + $remarkup_view = new PHUIRemarkupView($viewer, $text); 52 + $remarkup_view = phutil_tag( 53 + 'div', 54 + array( 55 + 'class' => 'phui-object-box-instructions', 56 + ), 57 + $remarkup_view); 58 + 59 + PhabricatorCookies::setClientIDCookie($request); 60 + 61 + $view = array(); 62 + foreach ($configs as $config) { 63 + $provider = $config->getProvider(); 64 + 65 + $form = $provider->buildLinkForm($this); 66 + 67 + if ($provider->isLoginFormAButton()) { 68 + require_celerity_resource('auth-css'); 69 + $form = phutil_tag( 70 + 'div', 71 + array( 72 + 'class' => 'phabricator-link-button pl', 73 + ), 74 + $form); 75 + } 76 + 77 + $view[] = $form; 78 + } 79 + 80 + $form = id(new AphrontFormView()) 81 + ->setViewer($viewer) 82 + ->appendControl( 83 + id(new AphrontFormSubmitControl()) 84 + ->addCancelButton('/', pht('Skip This Step'))); 85 + 86 + $header = id(new PHUIHeaderView()) 87 + ->setHeader(pht('Link External Account')); 88 + 89 + $box = id(new PHUIObjectBoxView()) 90 + ->setViewer($viewer) 91 + ->setHeader($header) 92 + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 93 + ->appendChild($remarkup_view) 94 + ->appendChild($view) 95 + ->appendChild($form); 96 + 97 + $main_view = id(new PHUITwoColumnView()) 98 + ->setFooter($box); 99 + 100 + $crumbs = $this->buildApplicationCrumbs() 101 + ->addTextCrumb(pht('Link External Account')) 102 + ->setBorder(true); 103 + 104 + return $this->newPage() 105 + ->setTitle(pht('Link External Account')) 106 + ->setCrumbs($crumbs) 107 + ->appendChild($main_view); 108 + } 109 + 110 + }
+18
src/applications/auth/message/PhabricatorAuthLinkMessageType.php
··· 1 + <?php 2 + 3 + final class PhabricatorAuthLinkMessageType 4 + extends PhabricatorAuthMessageType { 5 + 6 + const MESSAGEKEY = 'auth.link'; 7 + 8 + public function getDisplayName() { 9 + return pht('Unlinked Account Instructions'); 10 + } 11 + 12 + public function getShortDescription() { 13 + return pht( 14 + 'Guidance shown after a user logs in with an email link and is '. 15 + 'prompted to link an external account.'); 16 + } 17 + 18 + }
+1 -1
src/applications/auth/provider/PhabricatorAuthProvider.php
··· 161 161 abstract public function processLoginRequest( 162 162 PhabricatorAuthLoginController $controller); 163 163 164 - public function buildLinkForm(PhabricatorAuthLinkController $controller) { 164 + public function buildLinkForm($controller) { 165 165 return $this->renderLoginForm($controller->getRequest(), $mode = 'link'); 166 166 } 167 167
+1 -2
src/applications/auth/provider/PhabricatorPasswordAuthProvider.php
··· 159 159 return $dialog; 160 160 } 161 161 162 - public function buildLinkForm( 163 - PhabricatorAuthLinkController $controller) { 162 + public function buildLinkForm($controller) { 164 163 throw new Exception(pht("Password providers can't be linked.")); 165 164 } 166 165
+5
webroot/rsrc/css/phui/phui-object-box.css
··· 158 158 margin-top: 8px; 159 159 margin-bottom: 8px; 160 160 } 161 + 162 + .phui-object-box-instructions { 163 + padding: 16px; 164 + border-bottom: 1px solid {$thinblueborder}; 165 + }