@@ -26,6 +26,48 @@ function init_server_from_request()
2626 add_action ('plugins_loaded ' , __NAMESPACE__ . '\\init_server_from_request ' );
2727}
2828
29+ /**
30+ * Handle the second leg of the magic login on wp-login.php.
31+ *
32+ * CDN/proxy layers (e.g. Cloudflare with "Cache Everything") strip Set-Cookie
33+ * headers from responses on arbitrary URLs. wp-login.php is universally excluded
34+ * from CDN caching, so setting cookies here guarantees they reach the browser.
35+ */
36+ function handle_login_handoff ()
37+ {
38+ if (empty ($ _GET ['wp_cli_login_token ' ])) {
39+ return ;
40+ }
41+
42+ $ token = sanitize_text_field ($ _GET ['wp_cli_login_token ' ]);
43+ $ data = get_transient ('wp_cli_login/handoff/ ' . $ token );
44+ delete_transient ('wp_cli_login/handoff/ ' . $ token );
45+
46+ if (! $ data || ! ($ user = new \WP_User ($ data ['user_id ' ])) || ! $ user ->exists ()) {
47+ wp_die ('The magic login handoff has expired or is invalid. ' , '' , ['response ' => 410 ]);
48+ }
49+
50+ wp_set_auth_cookie ($ user ->ID );
51+ do_action ('wp_cli_login/login ' , $ user ->user_login , $ user );
52+ do_action ('wp_login ' , $ user ->user_login , $ user );
53+
54+ $ redirect_to = $ data ['redirect_url ' ] ?: admin_url ();
55+
56+ if ((empty ($ redirect_to ) || $ redirect_to == 'wp-admin/ ' || $ redirect_to == admin_url ())) {
57+ if (is_multisite () && ! get_active_blog_for_user ($ user ->ID ) && ! is_super_admin ($ user ->ID )) {
58+ $ redirect_to = user_admin_url ();
59+ } elseif (is_multisite () && ! $ user ->has_cap ('read ' )) {
60+ $ redirect_to = get_dashboard_url ($ user ->ID );
61+ } elseif (! $ user ->has_cap ('edit_posts ' )) {
62+ $ redirect_to = $ user ->has_cap ('read ' ) ? admin_url ('profile.php ' ) : home_url ();
63+ }
64+ }
65+
66+ wp_safe_redirect ($ redirect_to );
67+ exit ;
68+ }
69+ add_action ('login_init ' , __NAMESPACE__ . '\\handle_login_handoff ' );
70+
2971/**
3072 * @return bool
3173 */
@@ -127,14 +169,25 @@ public function checkEndpoint()
127169
128170 /**
129171 * Attempt the magic login.
172+ *
173+ * Rather than setting auth cookies directly (which CDN/proxy layers like
174+ * Cloudflare may strip), hand off to wp-login.php via a short-lived token.
130175 */
131176 public function run ()
132177 {
133178 try {
134179 $ magic = $ this ->loadMagic ();
135180 $ user = $ this ->validate ($ magic );
136- $ this ->loginUser ($ user );
137- $ this ->loginRedirect ($ user , $ magic ->redirect_url );
181+ $ this ->deleteMagic ();
182+
183+ $ token = bin2hex (random_bytes (16 ));
184+ set_transient ('wp_cli_login/handoff/ ' . $ token , [
185+ 'user_id ' => $ user ->ID ,
186+ 'redirect_url ' => $ magic ->redirect_url ,
187+ ], 30 );
188+
189+ wp_redirect (add_query_arg ('wp_cli_login_token ' , $ token , wp_login_url ()));
190+ exit ;
138191 } catch (Exception $ e ) {
139192 $ this ->deleteMagic ();
140193 $ this ->abort ($ e );
@@ -198,88 +251,6 @@ private function deleteMagic()
198251 delete_transient ($ this ->magicKey ());
199252 }
200253
201- /**
202- * Login the given user and redirect them to wp-admin.
203- *
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
233- *
234- * @param WP_User $user
235- * @param string $redirect_url
236- */
237- private function loginRedirect (WP_User $ user , $ redirect_url )
238- {
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- }
275-
276- /**
277- * Redirect safely to the URL provided.
278- */
279- wp_safe_redirect ($ redirect_to );
280- exit ;
281- }
282-
283254 /**
284255 * Abort the process; Explode with terrifying message.
285256 *
0 commit comments