@@ -26,6 +26,75 @@ function init_server_from_request()
2626 add_action ('plugins_loaded ' , __NAMESPACE__ . '\\init_server_from_request ' );
2727}
2828
29+ /**
30+ * Complete the magic login handoff on wp-login.php.
31+ *
32+ * CDN/proxy layers (e.g. Cloudflare with "Cache Everything") may strip
33+ * Set-Cookie headers from responses on arbitrary URLs. By deferring the
34+ * cookie-setting to wp-login.php — which CDNs universally exclude from
35+ * caching — the auth cookies are guaranteed to reach the browser.
36+ */
37+ function handle_login_handoff ()
38+ {
39+ if (empty ($ _GET ['wp_cli_login_token ' ])) {
40+ return ;
41+ }
42+
43+ $ token = sanitize_text_field ($ _GET ['wp_cli_login_token ' ]);
44+ $ data = get_transient ('wp_cli_login/handoff/ ' . $ token );
45+
46+ if (! $ data ) {
47+ wp_die (
48+ '<strong>The magic login handoff has expired or already been used.</strong> '
49+ . sprintf ('<p>Try again perhaps? or <a href="%s">Go Home →</a></p> ' , esc_url (home_url ())),
50+ 'Login Failed ' ,
51+ ['response ' => 410 ]
52+ );
53+ }
54+
55+ delete_transient ('wp_cli_login/handoff/ ' . $ token );
56+
57+ $ user = new \WP_User ($ data ['user_id ' ]);
58+
59+ if (! $ user ->exists ()) {
60+ wp_die (
61+ '<strong>No user found or no longer exists.</strong> '
62+ . sprintf ('<p><a href="%s">Go Home →</a></p> ' , esc_url (home_url ())),
63+ 'Login Failed ' ,
64+ ['response ' => 410 ]
65+ );
66+ }
67+
68+ nocache_headers ();
69+
70+ wp_set_auth_cookie ($ user ->ID );
71+
72+ /** @see WP_CLI_Login_Server::loginUser() */
73+ do_action ('wp_cli_login/login ' , $ user ->user_login , $ user );
74+ do_action ('wp_login ' , $ user ->user_login , $ user );
75+
76+ $ redirect_to = ! empty ($ data ['redirect_url ' ]) ? $ data ['redirect_url ' ] : admin_url ();
77+ $ redirect_to = apply_filters ('login_redirect ' , $ redirect_to , '' , $ user );
78+ $ redirect_to = apply_filters ('wp_cli_login/login_redirect ' , $ redirect_to , '' , $ user );
79+
80+ if ((empty ($ redirect_to ) || $ redirect_to == 'wp-admin/ ' || $ redirect_to == admin_url ())) {
81+ if (is_multisite () && ! get_active_blog_for_user ($ user ->ID ) && ! is_super_admin ($ user ->ID )) {
82+ $ redirect_to = user_admin_url ();
83+ } elseif (is_multisite () && ! $ user ->has_cap ('read ' )) {
84+ $ redirect_to = get_dashboard_url ($ user ->ID );
85+ } elseif (! $ user ->has_cap ('edit_posts ' )) {
86+ $ redirect_to = $ user ->has_cap ('read ' ) ? admin_url ('profile.php ' ) : home_url ();
87+ }
88+
89+ wp_redirect ($ redirect_to );
90+ exit ;
91+ }
92+
93+ wp_safe_redirect ($ redirect_to );
94+ exit ;
95+ }
96+ add_action ('login_init ' , __NAMESPACE__ . '\\handle_login_handoff ' );
97+
2998/**
3099 * @return bool
31100 */
@@ -133,8 +202,8 @@ public function run()
133202 try {
134203 $ magic = $ this ->loadMagic ();
135204 $ user = $ this ->validate ($ magic );
136- $ this ->loginUser ( $ user );
137- $ this ->loginRedirect ($ user , $ magic ->redirect_url );
205+ $ this ->deleteMagic ( );
206+ $ this ->handoffRedirect ($ user , $ magic ->redirect_url );
138207 } catch (Exception $ e ) {
139208 $ this ->deleteMagic ();
140209 $ this ->abort ($ e );
@@ -199,84 +268,30 @@ private function deleteMagic()
199268 }
200269
201270 /**
202- * Login the given user and redirect them to wp-admin .
271+ * Create a short-lived handoff token and redirect to wp-login.php .
203272 *
204- * @param WP_User $user
205- */
206- private function loginUser (WP_User $ user )
207- {
208- $ this ->deleteMagic ();
209-
210- wp_set_auth_cookie ($ user ->ID );
211-
212- /**
213- * Fires after the user has successfully logged in via the WP-CLI Login Server.
214- *
215- * @param string $user_login Username.
216- * @param WP_User $user WP_User object of the logged-in user.
217- */
218- do_action ('wp_cli_login/login ' , $ user ->user_login , $ user );
219-
220- /**
221- * Fires after the user has successfully logged in.
222- *
223- * @param string $user_login Username.
224- * @param WP_User $user WP_User object of the logged-in user.
225- */
226- do_action ('wp_login ' , $ user ->user_login , $ user );
227- }
228-
229- /**
230- * Redirect the user after logging in.
231- *
232- * Mostly copied from wp-login.php
273+ * CDN/proxy layers (e.g. Cloudflare with "Cache Everything") may strip
274+ * Set-Cookie headers from responses on arbitrary URLs. By redirecting to
275+ * wp-login.php — which CDNs universally exclude from caching — the auth
276+ * cookies are set on a response the browser is guaranteed to receive intact.
233277 *
234278 * @param WP_User $user
235279 * @param string $redirect_url
236280 */
237- private function loginRedirect (WP_User $ user , $ redirect_url )
281+ private function handoffRedirect (WP_User $ user , $ redirect_url )
238282 {
239- $ redirect_to = $ redirect_url ?: admin_url ();
240-
241- /**
242- * Filters the login redirect URL.
243- *
244- * @param string $redirect_to The redirect destination URL.
245- * @param string $requested_redirect_to The requested redirect destination URL passed as a parameter.
246- * @param WP_User $user WP_User object.
247- */
248- $ redirect_to = apply_filters ('login_redirect ' , $ redirect_to , '' , $ user );
249-
250- /**
251- * Filters the login redirect URL for WP-CLI Login Server requests.
252- *
253- * @param string $redirect_to The redirect destination URL.
254- * @param string $requested_redirect_to The requested redirect destination URL passed as a parameter.
255- * @param WP_User $user WP_User object.
256- */
257- $ redirect_to = apply_filters ('wp_cli_login/login_redirect ' , $ redirect_to , '' , $ user );
258-
259- /**
260- * Figure out where to redirect the user for the default wp-admin URL based on the user's capabilities.
261- */
262- if ((empty ($ redirect_to ) || $ redirect_to == 'wp-admin/ ' || $ redirect_to == admin_url ())) {
263- // If the user doesn't belong to a blog, send them to user admin. If the user can't edit posts, send them to their profile.
264- if (is_multisite () && ! get_active_blog_for_user ($ user ->ID ) && ! is_super_admin ($ user ->ID )) {
265- $ redirect_to = user_admin_url ();
266- } elseif (is_multisite () && ! $ user ->has_cap ('read ' )) {
267- $ redirect_to = get_dashboard_url ($ user ->ID );
268- } elseif (! $ user ->has_cap ('edit_posts ' )) {
269- $ redirect_to = $ user ->has_cap ('read ' ) ? admin_url ('profile.php ' ) : home_url ();
270- }
271-
272- wp_redirect ($ redirect_to );
273- exit ;
274- }
283+ $ token = bin2hex (random_bytes (16 ));
284+
285+ set_transient ('wp_cli_login/handoff/ ' . $ token , [
286+ 'user_id ' => $ user ->ID ,
287+ 'redirect_url ' => $ redirect_url ,
288+ ], 30 );
275289
276- /**
277- * Redirect safely to the URL provided.
278- */
279- wp_safe_redirect ($ redirect_to );
290+ nocache_headers ();
291+
292+ wp_redirect (
293+ add_query_arg ('wp_cli_login_token ' , $ token , wp_login_url ())
294+ );
280295 exit ;
281296 }
282297
0 commit comments