PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 2.4.0
Fluent Support – Helpdesk & Customer Support Ticket System v2.4.0
2.4.0 2.3.2 2.3.1 2.3.0 2.2.1 2.2.0 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.4.0 1.4.1 1.4.2 1.4.5 1.4.6 1.4.7 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 All 68 releases
← All changes | app/Http/Controllers/AuthController.php +189 -80 1.10.02.4.0 View file →
@@ -4,9 +4,9 @@
4 4
5 5 use FluentSupport\App\Models\Meta;
6 6 use FluentSupport\App\Services\Helper;
7 7 use FluentSupport\Framework\Support\Arr;
8 -use FluentSupport\Framework\Request\Request;
8 +use FluentSupport\Framework\Http\Request\Request;
9 9 use FluentSupport\App\Hooks\Handlers\AuthHandler;
10 10 use FluentSupport\App\Hooks\Handlers\ReCaptchaHandler;
11 11 use FluentSupport\App\Hooks\Handlers\TwoFaHandler;
12 12 use FluentSupport\App\Hooks\Handlers\EmailVerificationHandler;
@@ -22,15 +22,15 @@
22 22 */
23 23 public function signup(Request $request)
24 24 {
25 25
26 - if(Helper::getAuthProvider() != 'fluent_support') {
26 + if(Helper::getAuthProvider() !== 'fluent_support') {
27 27 return $this->sendError([
28 28 'message' => __('You are not allowed to signup using this form', 'fluent-support')
29 29 ]);
30 30 }
31 31
32 - if (!wp_verify_nonce($request->get('_fsupport_signup_nonce'), 'fluent_support_signup_nonce')) {
32 + if (!wp_verify_nonce($request->getSafe('_fsupport_signup_nonce', 'sanitize_text_field'), 'fluent_support_signup_nonce')) {
33 33 return $this->sendError([
34 34 'message' => __('Security verification failed. Please try again', 'fluent-support')
35 35 ]);
36 36 }
@@ -56,11 +56,17 @@
56 56 * @param array $formData
57 57 */
58 58 do_action('fluent_support/before_signup_validation', $formData);
59 59
60 - $checkRecaptchaAvailability = $this->isRecaptchaApplicable('signup_form');
61 - if ($checkRecaptchaAvailability && !$formData['_email_verification_hash']) {
62 - $validateCaptcha = ReCaptchaHandler::validateRecaptcha($formData['g-recaptcha-response']);
60 + // the verification code is only submitted on the second step, after the customer has
61 + // already passed the captcha and received the email; that step validates the hash
62 + // against an issued record below, so the captcha can be skipped there. The first
63 + // submit (which sends the verification email) always has to pass the captcha.
64 + $isVerificationStep = !empty($formData['_email_verification_token']);
65 +
66 + $checkRecaptchaAvailability = ReCaptchaHandler::isRecaptchaApplicable('signup_form');
67 + if ($checkRecaptchaAvailability && !$isVerificationStep) {
68 + $validateCaptcha = ReCaptchaHandler::validateRecaptcha($formData['g-recaptcha-response'] ?? '', null, null, 'signup');
63 69 if (!$validateCaptcha) {
64 70 return $this->response([
65 71 'message' => __('Your recaptcha is not verified', 'fluent-support')
66 72 ], 422);
@@ -68,9 +74,9 @@
68 74 }
69 75
70 76 $this->validate($formData, $rules, $messages);
71 77
72 - if (empty($formData['_email_verification_token'])) {
78 + if (!$isVerificationStep) {
73 79 $tokenHtml = EmailVerificationHandler::sendSignupEmailVerificationHtml($formData);
74 80
75 81 return $this->response([
76 82 'verification_html' => $tokenHtml
@@ -76,23 +82,51 @@
76 82 'verification_html' => $tokenHtml
77 83 ]);
78 84 } else {
79 85 $token = $formData['_email_verification_token'];
80 - $verificationHash = $formData['_email_verification_hash'];
86 + $verificationHash = $formData['_email_verification_hash'] ?? '';
81 87
82 - $logHash = Meta::where('object_type', 'fs_login_hashes',)
88 + $logHashMeta = Meta::where('object_type', 'fs_login_hashes',)
83 89 ->where('key', $verificationHash)
84 90 ->first();
85 - $logHash = Helper::safeUnserialize($logHash->value);
86 91
92 + if (!$logHashMeta) {
93 + wp_send_json([
94 + 'message' => __('Please provide a valid verification code that was sent to your email address', 'fluent-support')
95 + ], 422);
96 + }
97 +
98 + $logHash = Helper::safeUnserialize($logHashMeta->value);
99 +
87 100 if (!$logHash) {
88 101 wp_send_json([
89 - 'message' => __('Please provide a valid verification code that sent to your email address', 'fluent-support')
102 + 'message' => __('Please provide a valid verification code that was sent to your email address', 'fluent-support')
90 103 ], 422);
91 104 }
92 105
106 + // the code must still be unused and must not have been consumed by a prior request
107 + if (($logHash['status'] ?? '') !== 'issued') {
108 + wp_send_json([
109 + 'message' => __('Your verification code has already been used. Please try again', 'fluent-support')
110 + ], 422);
111 + }
112 +
113 + // records created before the email-binding fix (or any other legacy/malformed record)
114 + // have no bound email; treat them as invalid rather than proceeding with a null email
115 + if (empty($logHash['email'])) {
116 + wp_send_json([
117 + 'message' => __('Your verification code has expired. Please request a new one', 'fluent-support')
118 + ], 422);
119 + }
120 +
93 121 // check if it got expired or not
94 - if ($logHash['used_count'] > 5 || strtotime($logHash['valid_till']) < current_time('timestamp')) {
122 + // valid_till is written via gmdate() on a current_time('timestamp') basis (always
123 + // UTC-equivalent, since WP resets the runtime timezone to UTC on every request), so
124 + // it must be parsed back as UTC here - otherwise strtotime() would silently reinterpret
125 + // it under whatever timezone a plugin/theme may have switched to via
126 + // date_default_timezone_set() without restoring it, causing false expiry (or non-expiry)
127 + $validTill = $logHash['valid_till'] ?? '';
128 + if (($logHash['used_count'] ?? 0) > 5 || ($validTill && strtotime($validTill . ' UTC') < current_time('timestamp'))) {
95 129 wp_send_json([
96 130 'message' => __('Your verification code has been expired. Please try again', 'fluent-support')
97 131 ], 422);
98 132 }
@@ -104,18 +138,34 @@
104 138 'value' => maybe_serialize($logHash)
105 139 ]);
106 140
107 141 wp_send_json([
108 - 'message' => __('Please provide a valid verification code that sent to your email address', 'fluent-support')
142 + 'message' => __('Please provide a valid verification code that was sent to your email address', 'fluent-support')
109 143 ], 422);
110 144 }
111 145
112 - $logHash['used_count'] += 1;
113 - $logHash['status'] = 'used';
146 + // atomically consume the code: only succeeds if the record is still in the exact
147 + // state we just read, closing the race where two requests both pass the checks above
148 + $consumed = Meta::where('key', $logHash['login_hash'])
149 + ->where('object_type', 'fs_login_hashes')
150 + ->where('value', $logHashMeta->value)
151 + ->update([
152 + 'value' => maybe_serialize(array_merge($logHash, [
153 + 'used_count' => ($logHash['used_count'] ?? 0) + 1,
154 + 'status' => 'used',
155 + ]))
156 + ]);
114 157
115 - Meta::where('key', $logHash['login_hash'])->update([
116 - 'value' => maybe_serialize($logHash)
117 - ]);
158 + if (!$consumed) {
159 + wp_send_json([
160 + 'message' => __('Your verification code has already been used. Please try again', 'fluent-support')
161 + ], 422);
162 + }
163 +
164 + // the email is now server-verified for this code; ignore whatever the client
165 + // submitted and use the address the code was actually issued to, so the signup
166 + // can never be completed against a different (e.g. victim's) email address
167 + $formData['email'] = $logHash['email'];
118 168 }
119 169
120 170 /*
121 171 * Action After validate user signup validation success
@@ -168,15 +218,15 @@
168 218 * @return \WP_REST_Response
169 219 */
170 220 public function handleLogin(Request $request)
171 221 {
172 - if(Helper::getAuthProvider() != 'fluent_support') {
222 + if(Helper::getAuthProvider() !== 'fluent_support') {
173 223 return $this->sendError([
174 224 'message' => __('You are not allowed to login using this form', 'fluent-support')
175 225 ]);
176 226 }
177 227
178 - if (!wp_verify_nonce($request->get('_support_login_nonce'), 'fsupport_login_nonce')) {
228 + if (!wp_verify_nonce($request->getSafe('_support_login_nonce', 'sanitize_text_field'), 'fsupport_login_nonce')) {
179 229 return $this->response([
180 230 'message' => __('Security verification failed', 'fluent-support')
181 231 ], 403);
182 232 }
@@ -182,11 +232,11 @@
182 232 }
183 233
184 234 $data = $request->all();
185 235
186 - $checkRecaptchaAvailability = $this->isRecaptchaApplicable('login_form');
236 + $checkRecaptchaAvailability = ReCaptchaHandler::isRecaptchaApplicable('login_form');
187 237 if ($checkRecaptchaAvailability) {
188 - $validateCaptcha = ReCaptchaHandler::validateRecaptcha($data['g-recaptcha-response']);
238 + $validateCaptcha = ReCaptchaHandler::validateRecaptcha($data['g-recaptcha-response'] ?? '', null, null, 'login');
189 239
190 240 if (!$validateCaptcha) {
191 241 return $this->response([
192 242 'message' => __('Your recaptcha is not verified', 'fluent-support')
@@ -199,12 +249,10 @@
199 249 'message' => __('Email and Password is required', 'fluent-support')
200 250 ], 403);
201 251 }
202 252 $redirectUrl = Helper::getPortalBaseUrl();
203 - if ($redirect = $request->get('redirect_to')) {
204 - if (filter_var($redirect, FILTER_VALIDATE_URL)) {
205 - $redirectUrl = sanitize_url($redirect);
206 - }
253 + if ($redirect = $request->getSafe('redirect_to', 'sanitize_text_field')) {
254 + $redirectUrl = wp_validate_redirect($redirect, $redirectUrl);
207 255 }
208 256
209 257 if (get_current_user_id()) { // user already registered
210 258 return $this->sendSuccess([
@@ -212,9 +260,9 @@
212 260 ]);
213 261 }
214 262
215 263 $email = sanitize_user($data['log']);
216 - $password = trim($data['pwd']);
264 + $password = trim($data['pwd'] ?? '');
217 265
218 266 if (is_email($email)) {
219 267 $user = get_user_by('email', $email);
220 268 } else {
@@ -220,12 +268,32 @@
220 268 } else {
221 269 $user = get_user_by('login', $email);
222 270 }
223 271
272 + // Rate limiting: per-IP bucket (5 attempts) + per-account bucket (20 attempts)
273 + $ip = Helper::getIp();
274 + $ipKey = $user
275 + ? 'fs_login_ip_' . wp_hash($user->ID . '|' . $ip)
276 + : 'fs_login_ip_' . wp_hash(strtolower($email) . '|' . $ip);
277 + $accountKey = $user
278 + ? 'fs_login_act_' . wp_hash($user->ID)
279 + : 'fs_login_act_' . wp_hash(strtolower($email));
280 +
281 + $ipAttempts = get_transient($ipKey);
282 + $accountAttempts = get_transient($accountKey);
283 +
284 + if (($ipAttempts !== false && $ipAttempts >= 5) || ($accountAttempts !== false && $accountAttempts >= 20)) {
285 + return $this->sendError([
286 + 'message' => __('Too many login attempts. Please try again after 15 minutes.', 'fluent-support')
287 + ], 429);
288 + }
289 +
224 290 if (!$user) {
225 - $user = new \WP_Error('authentication_failed', __('<strong>Error</strong>: Invalid username, email address or incorrect password.', 'fluent-support'));
291 + $user = new \WP_Error('authentication_failed', __('Invalid username, email address or incorrect password.', 'fluent-support'));
226 292
227 293 do_action('wp_login_failed', $email, $user);
294 + $this->incrementLoginAttempts($ipKey);
295 + $this->incrementLoginAttempts($accountKey);
228 296
229 297 return $this->response([
230 298 'message' => __('Email or Password is not valid. Please try again', 'fluent-support')
231 299 ], 403);
@@ -232,9 +300,18 @@
232 300
233 301 }
234 302
235 303 $twoFactorEnabled = Helper::getBusinessSettings('enable_two_fa');
236 - if ('yes' == $twoFactorEnabled) {
304 + if ('yes' === $twoFactorEnabled) {
305 + if (!wp_check_password($password, $user->user_pass, $user->ID)) {
306 + $this->incrementLoginAttempts($ipKey);
307 + $this->incrementLoginAttempts($accountKey);
308 +
309 + return $this->response([
310 + 'message' => __('Invalid username, email address or incorrect password.', 'fluent-support')
311 + ], 403);
312 + }
313 +
237 314 (new TwoFaHandler)->maybe2FaRedirect($user);
238 315 }
239 316
240 317 if (apply_filters('fluent_support_use_native_login', true)) {
@@ -239,13 +316,20 @@
239 316
240 317 if (apply_filters('fluent_support_use_native_login', true)) {
241 318 $user = wp_signon();
242 319 if (is_wp_error($user)) {
320 + $this->incrementLoginAttempts($ipKey);
321 + $this->incrementLoginAttempts($accountKey);
243 322 return $this->response([
244 323 'message' => $user->get_error_message()
245 324 ], 403);
246 325 }
247 326
327 + // Clear rate limits for the authenticated user
328 + $authIpKey = 'fs_login_ip_' . wp_hash($user->ID . '|' . $ip);
329 + $authAccountKey = 'fs_login_act_' . wp_hash($user->ID);
330 + delete_transient($authIpKey);
331 + delete_transient($authAccountKey);
248 332 return $this->sendSuccess([
249 333 'redirect' => $redirectUrl
250 334 ]);
251 335 }
@@ -250,8 +334,10 @@
250 334 ]);
251 335 }
252 336
253 337 if (wp_check_password($password, $user->user_pass, $user->ID)) {
338 + delete_transient($ipKey);
339 + delete_transient($accountKey);
254 340 $this->login($user->ID);
255 341 return $this->sendSuccess([
256 342 'redirect' => $redirectUrl
257 343 ]);
@@ -256,29 +342,24 @@
256 342 'redirect' => $redirectUrl
257 343 ]);
258 344 }
259 345
346 + $this->incrementLoginAttempts($ipKey);
347 + $this->incrementLoginAttempts($accountKey);
348 +
260 349 return $this->response([
261 - 'message' => __('<strong>Error</strong>: Invalid username, email address or incorrect password.', 'fluent-support')
350 + 'message' => __('Invalid username, email address or incorrect password.', 'fluent-support')
262 351 ], 403);
263 352 }
264 353
265 - public function isRecaptchaApplicable($formName)
354 + private function incrementLoginAttempts($rateLimitKey)
266 355 {
267 - $reCaptchaSettingsData = Meta::where('object_type', '_fs_recaptcha_settings')->first();
268 - if(!isset($reCaptchaSettingsData->value)){
269 - return false;
356 + $attempts = get_transient($rateLimitKey);
357 + if ($attempts === false) {
358 + set_transient($rateLimitKey, 1, 15 * MINUTE_IN_SECONDS);
359 + } else {
360 + set_transient($rateLimitKey, $attempts + 1, 15 * MINUTE_IN_SECONDS);
270 361 }
271 - $reCaptchaData = Helper::safeUnserialize($reCaptchaSettingsData->value, []);
272 - if(!isset($reCaptchaData['is_enabled']) || !isset($reCaptchaData['formContainingReCaptcha'])){
273 - return false;
274 - }
275 - $isEnabled = filter_var($reCaptchaData['is_enabled'], FILTER_VALIDATE_BOOLEAN);
276 - if (!$isEnabled) {
277 - return false;
278 - }
279 - $formContainingReCaptcha = $reCaptchaData['formContainingReCaptcha'];
280 - return 'yes' === $formContainingReCaptcha[$formName];
281 362 }
282 363
283 364 private function nativeLoginHandler($user, $info, $redirectUrl = '')
284 365 {
@@ -305,11 +386,13 @@
305 386 }
306 387
307 388 $user_signon = wp_signon($info, $secure_cookie);
308 389
390 + // Note: No sanitization needed here as we're only checking emptiness, not using the cookie value
309 391 if (!is_wp_error($user_signon) && empty($_COOKIE[LOGGED_IN_COOKIE])) {
310 392 if (headers_sent()) {
311 393 return $this->response([
394 + // translators: %1$s is the URL to WordPress cookies documentation, %2$s is the URL to WordPress support forums
312 395 'message' => sprintf(__('<strong>ERROR</strong>: Cookies are blocked due to unexpected output. For help, please see <a href="%1$s">this documentation</a> or try the <a href="%2$s">support forums</a>.', 'fluent-support'),
313 396 'https://codex.wordpress.org/Cookies', 'https://wordpress.org/support/')
314 397 ], 403);
315 398 }
@@ -378,9 +461,9 @@
378 461
379 462 public function resetPassword(Request $request)
380 463 {
381 464
382 - if(Helper::getAuthProvider() != 'fluent_support') {
465 + if(Helper::getAuthProvider() !== 'fluent_support') {
383 466 return $this->sendError([
384 467 'message' => __('You are not allowed to reset password using this form', 'fluent-support')
385 468 ]);
386 469 }
@@ -386,15 +469,15 @@
386 469 }
387 470
388 471 $errors = new \WP_Error();
389 472
390 - if (!wp_verify_nonce($request->get('_fsupport_reset_pass_nonce'), 'fluent_support_reset_pass_nonce')) {
473 + if (!wp_verify_nonce($request->getSafe('_fsupport_reset_pass_nonce', 'sanitize_text_field'), 'fluent_support_reset_pass_nonce')) {
391 474 return $this->sendError([
392 475 'message' => __('Security verification failed. Please try again', 'fluent-support')
393 476 ]);
394 477 }
395 478
396 - $usernameOrEmail = trim(wp_unslash($request->get('user_login')));
479 + $usernameOrEmail = trim(wp_unslash($request->getSafe('user_login', 'sanitize_text_field')));
397 480
398 481 if (!$usernameOrEmail) {
399 482 return $this->sendError([
400 483 'message' => 'Username or email is required'
@@ -400,8 +483,18 @@
400 483 'message' => 'Username or email is required'
401 484 ]);
402 485 }
403 486
487 + // IP bucket is a generous volumetric backstop (shared office/NAT IPs can have many
488 + // unrelated users). It runs before the account lookup so that probes for accounts
489 + // that don't exist are throttled too. Keyed on the IP only, so a 429 here reveals
490 + // nothing about whether any given account exists.
491 + if (Helper::hitRateLimit('fs_reset_pass_ip_' . wp_hash(Helper::getIp()), 20)) {
492 + return $this->sendError([
493 + 'message' => __('Too many password reset requests. Please try again after 15 minutes.', 'fluent-support')
494 + ], 429);
495 + }
496 +
404 497 $user_data = get_user_by('email', $usernameOrEmail);
405 498
406 499 if (!$user_data) {
407 500 $user_data = get_user_by('login', $usernameOrEmail);
@@ -407,11 +500,9 @@
407 500 $user_data = get_user_by('login', $usernameOrEmail);
408 501 }
409 502
410 503 if (!$user_data) {
411 - return $this->sendError([
412 - 'message' => __('Invalid username or email', 'fluent-support')
413 - ]);
504 + return $this->sendResetPassResponse();
414 505 }
415 506
416 507 $user_data = apply_filters('lostpassword_user_data', $user_data, $errors);
417 508
@@ -419,24 +510,17 @@
419 510
420 511 $errors = apply_filters('lostpassword_errors', $errors, $user_data);
421 512
422 513 if ($errors->has_errors()) {
423 - return $this->sendError([
424 - 'message' => $errors->get_error_message()
425 - ]);
514 + return $this->sendResetPassResponse();
426 515 }
427 516
428 517 if (!$user_data) {
429 - return $this->sendError([
430 - 'message' => __('<strong>Error</strong>: There is no account with that username or email address.', 'fluent-support')
431 - ]);
518 + return $this->sendResetPassResponse();
432 519 }
433 520
434 521 if (is_multisite() && !is_user_member_of_blog($user_data->ID, get_current_blog_id())) {
435 -
436 - return $this->sendError([
437 - 'message' => __('<strong>Error</strong>: Invalid username or email', 'fluent-support')
438 - ]);
522 + return $this->sendResetPassResponse();
439 523 }
440 524
441 525 // Redefining user_login ensures we return the right case in the email.
442 526 $user_login = $user_data->user_login;
@@ -444,21 +528,13 @@
444 528 do_action('retrieve_password', $user_login);
445 529
446 530 $allow = apply_filters('allow_password_reset', true, $user_data->ID);
447 531
448 - if (!$allow) {
449 - return $this->sendError([
450 - 'message' => __('Password reset is not allowed for this user', 'fluent-support')
451 - ]);
532 + if (!$allow || is_wp_error($allow)) {
533 + return $this->sendResetPassResponse();
452 534 }
453 535
454 - if (is_wp_error($allow)) {
455 - return $this->sendError([
456 - 'message' => $allow->get_error_message()
457 - ]);
458 - }
459 536
460 -
461 537 /*
462 538 * Filter reset password link text
463 539 *
464 540 * @since v1.5.7
@@ -463,10 +539,24 @@
463 539 *
464 540 * @since v1.5.7
465 541 * @param string $linkText
466 542 */
543 + // translators: %s is the site name
467 544 $linkText = apply_filters("fluent_support/reset_password_link", sprintf(__('Reset your password for %s', 'fluent-support'), get_bloginfo('name')));
468 545
546 + // Issuance cooldown. get_password_reset_key() rotates the stored key, invalidating
547 + // any link already sitting in the account owner's inbox, so an unthrottled caller
548 + // could deny password recovery indefinitely. Suppressing the duplicate issuance is
549 + // safe: reset mail only ever goes to the account owner, so whoever triggered the
550 + // first send has already put a working link in that inbox.
551 + $cooldownKey = 'fs_reset_pass_sent_' . wp_hash($user_data->ID);
552 +
553 + if (get_transient($cooldownKey)) {
554 + return $this->sendResetPassResponse();
555 + }
556 +
557 + set_transient($cooldownKey, 1, 5 * MINUTE_IN_SECONDS);
558 +
469 559 $resetUrl = add_query_arg([
470 560 'action' => 'rp',
471 561 'key' => get_password_reset_key($user_data),
472 562 'login' => rawurlencode($user_data->user_login)
@@ -479,15 +569,18 @@
479 569 *
480 570 * @since v1.5.7
481 571 * @param string $mailSubject
482 572 */
573 + // translators: %s is the site name
483 574 $mailSubject = apply_filters("fluent_support/reset_password_mail_subject", sprintf(__('Reset your password for %s support portal', 'fluent-support'), get_bloginfo('name')));
484 575
485 - $message = sprintf(__('<p>Hi %s,</p>', 'fluent-support'), $user_data->first_name) .
486 - __('<p>Someone has requested a new password for the following account on WordPress:</p>', 'fluent-support') .
487 - sprintf(__('<p>Username: %s</p>', 'fluent-support'), $user_login) .
488 - sprintf(__('<p>%s</p>', 'fluent-support'), $resetLink) .
489 - sprintf(__('<p>If you did not request to reset your password, please ignore this email.</p>', 'fluent-support'));
576 + // translators: %s is the user's first name
577 + $message = '<p>' . sprintf(__('Hi %s,', 'fluent-support'), $user_data->first_name) . '</p>' .
578 + '<p>' . __('Someone has requested a new password for the following account on WordPress:', 'fluent-support') . '</p>' .
579 + // translators: %s is the username
580 + '<p>' . sprintf(__('Username: %s', 'fluent-support'), $user_login) . '</p>' .
581 + '<p>' . $resetLink . '</p>' .
582 + '<p>' . __('If you did not request to reset your password, please ignore this email.', 'fluent-support') . '</p>';
490 583
491 584 /*
492 585 * Filter reset password email body text
493 586 *
@@ -501,10 +594,24 @@
501 594 $headers = array('Content-Type: text/html; charset=UTF-8');
502 595
503 596 wp_mail($user_data->user_email, $mailSubject, $message, $headers);
504 597
598 + return $this->sendResetPassResponse();
599 + }
600 +
601 + /**
602 + * Single response for every password reset outcome.
603 + *
604 + * Whether the account exists, is disallowed, is not a member of this site or is
605 + * inside the resend cooldown, the caller sees the same thing — otherwise the form
606 + * confirms which usernames and email addresses are real.
607 + *
608 + * @return mixed
609 + */
610 + protected function sendResetPassResponse()
611 + {
505 612 return $this->sendSuccess([
506 - 'message' => __('Please check your email for the reset link', 'fluent-support')
613 + 'message' => __('If an account matches that username or email, a password reset link has been sent. Please check your inbox, including the spam folder.', 'fluent-support')
507 614 ]);
508 615 }
509 616
510 617 /**
@@ -547,9 +654,9 @@
547 654 */
548 655 $userName = apply_filters('fluent_support/signup_username', Arr::get($formData, 'username'));
549 656
550 657 if (empty($formData['password'])) {
551 - $password = wp_generate_password(8);
658 + $password = wp_generate_password(16, true, true);
552 659 } else {
553 660 $password = $formData['password'];
554 661 }
555 662
@@ -587,16 +694,18 @@
587 694 * @param $formData
588 695 */
589 696 public function maybeUpdateUser($userId, $formData)
590 697 {
591 - $name = trim(Arr::get($formData, 'first_name') . ' ' . Arr::get($formData, 'last_name'));
698 + $firstName = Arr::get($formData, 'first_name', '');
699 + $lastName = Arr::get($formData, 'last_name', '');
700 + $name = trim($firstName . ' ' . $lastName);
592 701
593 702 $data = array_filter([
594 703 'ID' => $userId,
595 704 'user_nicename' => $name,
596 705 'display_name' => $name,
597 - 'first_name' => Arr::get($formData, 'first_name'),
598 - 'last_name' => Arr::get($formData, 'last_name'),
706 + 'first_name' => $firstName,
707 + 'last_name' => $lastName,
599 708 ]);
600 709
601 710 if ($name) {
602 711 /*