PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 2.4.5
Fluent Support – Helpdesk & Customer Support Ticket System v2.4.5
2.4.5 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 All 69 releases
← All changes | app/Http/Controllers/AuthController.php +205 -105 1.10.1 → 2.4.5 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;
@@ -14,9 +14,11 @@
14 14
15 15 class AuthController extends Controller
16 16 {
17 17 /**
18 - * signUp method will create new user submitted data from sign up form
18 + * Two steps: the first submit emails a verification code, the second
19 + * verifies it and creates the user.
20 + *
19 21 * @param Request $request
20 22 * @return \WP_REST_Response
21 23 * @throws \FluentSupport\Framework\Validator\ValidationException
22 24 */
@@ -22,16 +24,22 @@
22 24 */
23 25 public function signup(Request $request)
24 26 {
25 27
26 - if(Helper::getAuthProvider() != 'fluent_support') {
28 + if(Helper::getAuthProvider() !== 'fluent_support') {
27 29 return $this->sendError([
28 30 'message' => __('You are not allowed to signup using this form', 'fluent-support')
29 31 ]);
30 32 }
31 33
32 - if (!wp_verify_nonce($request->get('_fsupport_signup_nonce'), 'fluent_support_signup_nonce')) {
34 + if (!Helper::isSignupEnabled()) {
33 35 return $this->sendError([
36 + 'message' => __('User registration is not enabled on this site.', 'fluent-support')
37 + ], 403);
38 + }
39 +
40 + if (!wp_verify_nonce($request->getSafe('_fsupport_signup_nonce', 'sanitize_text_field'), 'fluent_support_signup_nonce')) {
41 + return $this->sendError([
34 42 'message' => __('Security verification failed. Please try again', 'fluent-support')
35 43 ]);
36 44 }
37 45
@@ -56,11 +64,15 @@
56 64 * @param array $formData
57 65 */
58 66 do_action('fluent_support/before_signup_validation', $formData);
59 67
60 - $checkRecaptchaAvailability = $this->isRecaptchaApplicable('signup_form');
61 - if ($checkRecaptchaAvailability && !$formData['_email_verification_hash']) {
62 - $validateCaptcha = ReCaptchaHandler::validateRecaptcha($formData['g-recaptcha-response']);
68 + // The captcha was already passed on the first step, and the second step is
69 + // gated by the issued verification record below.
70 + $isVerificationStep = !empty($formData['_email_verification_token']);
71 +
72 + $checkRecaptchaAvailability = ReCaptchaHandler::isRecaptchaApplicable('signup_form');
73 + if ($checkRecaptchaAvailability && !$isVerificationStep) {
74 + $validateCaptcha = ReCaptchaHandler::validateRecaptcha($formData['g-recaptcha-response'] ?? '', null, null, 'signup');
63 75 if (!$validateCaptcha) {
64 76 return $this->response([
65 77 'message' => __('Your recaptcha is not verified', 'fluent-support')
66 78 ], 422);
@@ -68,9 +80,9 @@
68 80 }
69 81
70 82 $this->validate($formData, $rules, $messages);
71 83
72 - if (empty($formData['_email_verification_token'])) {
84 + if (!$isVerificationStep) {
73 85 $tokenHtml = EmailVerificationHandler::sendSignupEmailVerificationHtml($formData);
74 86
75 87 return $this->response([
76 88 'verification_html' => $tokenHtml
@@ -76,24 +88,46 @@
76 88 'verification_html' => $tokenHtml
77 89 ]);
78 90 } else {
79 91 $token = $formData['_email_verification_token'];
80 - $verificationHash = $formData['_email_verification_hash'];
92 + $verificationHash = $formData['_email_verification_hash'] ?? '';
81 93
82 - $logHash = Meta::where('object_type', 'fs_login_hashes',)
94 + $logHashMeta = Meta::where('object_type', 'fs_login_hashes',)
83 95 ->where('key', $verificationHash)
84 96 ->first();
85 - $logHash = Helper::safeUnserialize($logHash->value);
86 97
98 + if (!$logHashMeta) {
99 + wp_send_json([
100 + 'message' => __('Please provide a valid verification code that was sent to your email address', 'fluent-support')
101 + ], 422);
102 + }
103 +
104 + $logHash = Helper::safeUnserialize($logHashMeta->value);
105 +
87 106 if (!$logHash) {
88 107 wp_send_json([
89 - 'message' => __('Please provide a valid verification code that sent to your email address', 'fluent-support')
108 + 'message' => __('Please provide a valid verification code that was sent to your email address', 'fluent-support')
90 109 ], 422);
91 110 }
92 111
93 - // check if it got expired or not
94 - if ($logHash['used_count'] > 5 || strtotime($logHash['valid_till']) < current_time('timestamp')) {
112 + if (($logHash['status'] ?? '') !== 'issued') {
95 113 wp_send_json([
114 + 'message' => __('Your verification code has already been used. Please try again', 'fluent-support')
115 + ], 422);
116 + }
117 +
118 + // Old or malformed records have no bound email.
119 + if (empty($logHash['email'])) {
120 + wp_send_json([
121 + 'message' => __('Your verification code has expired. Please request a new one', 'fluent-support')
122 + ], 422);
123 + }
124 +
125 + // valid_till is written with gmdate(). Parse it as UTC explicitly, since a plugin
126 + // may have changed the PHP default timezone.
127 + $validTill = $logHash['valid_till'] ?? '';
128 + if (($logHash['used_count'] ?? 0) > 5 || ($validTill && strtotime($validTill . ' UTC') < current_time('timestamp'))) {
129 + wp_send_json([
96 130 'message' => __('Your verification code has been expired. Please try again', 'fluent-support')
97 131 ], 422);
98 132 }
99 133
@@ -104,18 +138,32 @@
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 + // Consume atomically: the update only matches if the record is unchanged since
147 + // we read it, so two concurrent requests can't both succeed.
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 + // Use the address the code was issued to, not the submitted one.
165 + $formData['email'] = $logHash['email'];
118 166 }
119 167
120 168 /*
121 169 * Action After validate user signup validation success
@@ -126,13 +174,13 @@
126 174 do_action('fluent_support/after_signup_validation', $formData);
127 175
128 176 $userId = $this->createUser($formData);
129 177
130 - if (is_wp_error($userId)) {
178 + if (!$userId) {
131 179 return $this->response(
132 180 apply_filters(
133 181 'fluent_support/signup_create_user_error',
134 - ['error' => $userId->get_error_message()]
182 + ['error' => __('Could not create your account. The username or email address may already be registered.', 'fluent-support')]
135 183 ), 423);
136 184 }
137 185
138 186 /*
@@ -138,9 +186,8 @@
138 186 /*
139 187 * Action After creating WP user from ticket sign up form
140 188 *
141 189 * @since v1.0.0
142 - * @param array $formData
143 190 */
144 191 do_action('fluent_support/after_creating_user');
145 192
146 193 $this->maybeUpdateUser($userId, $formData);
@@ -158,25 +205,30 @@
158 205 'message' => __('Successfully registered to the site.', 'fluent-support'),
159 206 'redirect' => Arr::get($formData, '__redirect_to', Helper::getPortalBaseUrl())
160 207 ]);
161 208
209 + // The browser navigates to this by script; keep it on this site.
210 + if (!empty($response['redirect'])) {
211 + $portalUrl = Helper::getPortalBaseUrl();
212 + $response['redirect'] = wp_validate_redirect(esc_url_raw($response['redirect']), $portalUrl) ?: $portalUrl;
213 + }
214 +
162 215 return $this->response($response);
163 216 }
164 217
165 218 /**
166 - * handleLogin method will perform login functionality and redirect
167 219 * @param Request $request
168 220 * @return \WP_REST_Response
169 221 */
170 222 public function handleLogin(Request $request)
171 223 {
172 - if(Helper::getAuthProvider() != 'fluent_support') {
224 + if(Helper::getAuthProvider() !== 'fluent_support') {
173 225 return $this->sendError([
174 226 'message' => __('You are not allowed to login using this form', 'fluent-support')
175 227 ]);
176 228 }
177 229
178 - if (!wp_verify_nonce($request->get('_support_login_nonce'), 'fsupport_login_nonce')) {
230 + if (!wp_verify_nonce($request->getSafe('_support_login_nonce', 'sanitize_text_field'), 'fsupport_login_nonce')) {
179 231 return $this->response([
180 232 'message' => __('Security verification failed', 'fluent-support')
181 233 ], 403);
182 234 }
@@ -182,11 +234,11 @@
182 234 }
183 235
184 236 $data = $request->all();
185 237
186 - $checkRecaptchaAvailability = $this->isRecaptchaApplicable('login_form');
238 + $checkRecaptchaAvailability = ReCaptchaHandler::isRecaptchaApplicable('login_form');
187 239 if ($checkRecaptchaAvailability) {
188 - $validateCaptcha = ReCaptchaHandler::validateRecaptcha($data['g-recaptcha-response']);
240 + $validateCaptcha = ReCaptchaHandler::validateRecaptcha($data['g-recaptcha-response'] ?? '', null, null, 'login');
189 241
190 242 if (!$validateCaptcha) {
191 243 return $this->response([
192 244 'message' => __('Your recaptcha is not verified', 'fluent-support')
@@ -199,15 +251,13 @@
199 251 'message' => __('Email and Password is required', 'fluent-support')
200 252 ], 403);
201 253 }
202 254 $redirectUrl = Helper::getPortalBaseUrl();
203 - if ($redirect = $request->get('redirect_to')) {
204 - if (filter_var($redirect, FILTER_VALIDATE_URL)) {
205 - $redirectUrl = sanitize_url($redirect);
206 - }
255 + if ($redirect = $request->getSafe('redirect_to', 'sanitize_text_field')) {
256 + $redirectUrl = wp_validate_redirect($redirect, $redirectUrl);
207 257 }
208 258
209 - if (get_current_user_id()) { // user already registered
259 + if (get_current_user_id()) { // already logged in
210 260 return $this->sendSuccess([
211 261 'redirect' => $redirectUrl
212 262 ]);
213 263 }
@@ -212,9 +262,9 @@
212 262 ]);
213 263 }
214 264
215 265 $email = sanitize_user($data['log']);
216 - $password = trim($data['pwd']);
266 + $password = trim($data['pwd'] ?? '');
217 267
218 268 if (is_email($email)) {
219 269 $user = get_user_by('email', $email);
220 270 } else {
@@ -220,12 +270,32 @@
220 270 } else {
221 271 $user = get_user_by('login', $email);
222 272 }
223 273
274 + // 5 attempts per account+IP, 20 per account.
275 + $ip = Helper::getIp();
276 + $ipKey = $user
277 + ? 'fs_login_ip_' . wp_hash($user->ID . '|' . $ip)
278 + : 'fs_login_ip_' . wp_hash(strtolower($email) . '|' . $ip);
279 + $accountKey = $user
280 + ? 'fs_login_act_' . wp_hash($user->ID)
281 + : 'fs_login_act_' . wp_hash(strtolower($email));
282 +
283 + $ipAttempts = get_transient($ipKey);
284 + $accountAttempts = get_transient($accountKey);
285 +
286 + if (($ipAttempts !== false && $ipAttempts >= 5) || ($accountAttempts !== false && $accountAttempts >= 20)) {
287 + return $this->sendError([
288 + 'message' => __('Too many login attempts. Please try again after 15 minutes.', 'fluent-support')
289 + ], 429);
290 + }
291 +
224 292 if (!$user) {
225 - $user = new \WP_Error('authentication_failed', __('<strong>Error</strong>: Invalid username, email address or incorrect password.', 'fluent-support'));
293 + $user = new \WP_Error('authentication_failed', __('Invalid username, email address or incorrect password.', 'fluent-support'));
226 294
227 295 do_action('wp_login_failed', $email, $user);
296 + $this->incrementLoginAttempts($ipKey);
297 + $this->incrementLoginAttempts($accountKey);
228 298
229 299 return $this->response([
230 300 'message' => __('Email or Password is not valid. Please try again', 'fluent-support')
231 301 ], 403);
@@ -232,9 +302,18 @@
232 302
233 303 }
234 304
235 305 $twoFactorEnabled = Helper::getBusinessSettings('enable_two_fa');
236 - if ('yes' == $twoFactorEnabled) {
306 + if ('yes' === $twoFactorEnabled) {
307 + if (!wp_check_password($password, $user->user_pass, $user->ID)) {
308 + $this->incrementLoginAttempts($ipKey);
309 + $this->incrementLoginAttempts($accountKey);
310 +
311 + return $this->response([
312 + 'message' => __('Invalid username, email address or incorrect password.', 'fluent-support')
313 + ], 403);
314 + }
315 +
237 316 (new TwoFaHandler)->maybe2FaRedirect($user);
238 317 }
239 318
240 319 if (apply_filters('fluent_support_use_native_login', true)) {
@@ -239,13 +318,19 @@
239 318
240 319 if (apply_filters('fluent_support_use_native_login', true)) {
241 320 $user = wp_signon();
242 321 if (is_wp_error($user)) {
322 + $this->incrementLoginAttempts($ipKey);
323 + $this->incrementLoginAttempts($accountKey);
243 324 return $this->response([
244 325 'message' => $user->get_error_message()
245 326 ], 403);
246 327 }
247 328
329 + $authIpKey = 'fs_login_ip_' . wp_hash($user->ID . '|' . $ip);
330 + $authAccountKey = 'fs_login_act_' . wp_hash($user->ID);
331 + delete_transient($authIpKey);
332 + delete_transient($authAccountKey);
248 333 return $this->sendSuccess([
249 334 'redirect' => $redirectUrl
250 335 ]);
251 336 }
@@ -250,8 +335,10 @@
250 335 ]);
251 336 }
252 337
253 338 if (wp_check_password($password, $user->user_pass, $user->ID)) {
339 + delete_transient($ipKey);
340 + delete_transient($accountKey);
254 341 $this->login($user->ID);
255 342 return $this->sendSuccess([
256 343 'redirect' => $redirectUrl
257 344 ]);
@@ -256,29 +343,24 @@
256 343 'redirect' => $redirectUrl
257 344 ]);
258 345 }
259 346
347 + $this->incrementLoginAttempts($ipKey);
348 + $this->incrementLoginAttempts($accountKey);
349 +
260 350 return $this->response([
261 - 'message' => __('<strong>Error</strong>: Invalid username, email address or incorrect password.', 'fluent-support')
351 + 'message' => __('Invalid username, email address or incorrect password.', 'fluent-support')
262 352 ], 403);
263 353 }
264 354
265 - public function isRecaptchaApplicable($formName)
355 + private function incrementLoginAttempts($rateLimitKey)
266 356 {
267 - $reCaptchaSettingsData = Meta::where('object_type', '_fs_recaptcha_settings')->first();
268 - if(!isset($reCaptchaSettingsData->value)){
269 - return false;
357 + $attempts = get_transient($rateLimitKey);
358 + if ($attempts === false) {
359 + set_transient($rateLimitKey, 1, 15 * MINUTE_IN_SECONDS);
360 + } else {
361 + set_transient($rateLimitKey, $attempts + 1, 15 * MINUTE_IN_SECONDS);
270 362 }
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 363 }
282 364
283 365 private function nativeLoginHandler($user, $info, $redirectUrl = '')
284 366 {
@@ -305,11 +387,13 @@
305 387 }
306 388
307 389 $user_signon = wp_signon($info, $secure_cookie);
308 390
391 + // Only checked for emptiness, so no sanitizing needed.
309 392 if (!is_wp_error($user_signon) && empty($_COOKIE[LOGGED_IN_COOKIE])) {
310 393 if (headers_sent()) {
311 394 return $this->response([
395 + // translators: %1$s is the URL to WordPress cookies documentation, %2$s is the URL to WordPress support forums
312 396 '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 397 'https://codex.wordpress.org/Cookies', 'https://wordpress.org/support/')
314 398 ], 403);
315 399 }
@@ -344,9 +428,8 @@
344 428 ]);
345 429 }
346 430
347 431 /**
348 - * getRules method will prepare the rules for the input field
349 432 * @param array $fields
350 433 * @return mixed
351 434 */
352 435 protected function getRules($fields = [])
@@ -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,22 +469,30 @@
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 - 'message' => 'Username or email is required'
483 + 'message' => __('Username or email is required.', 'fluent-support')
401 484 ]);
402 485 }
403 486
487 + // Generous because of shared NAT IPs. Runs before the account lookup so probes for
488 + // missing accounts are throttled too, and a 429 reveals nothing about existence.
489 + if (Helper::hitRateLimit('fs_reset_pass_ip_' . wp_hash(Helper::getIp()), 20)) {
490 + return $this->sendError([
491 + 'message' => __('Too many password reset requests. Please try again after 15 minutes.', 'fluent-support')
492 + ], 429);
493 + }
494 +
404 495 $user_data = get_user_by('email', $usernameOrEmail);
405 496
406 497 if (!$user_data) {
407 498 $user_data = get_user_by('login', $usernameOrEmail);
@@ -407,11 +498,9 @@
407 498 $user_data = get_user_by('login', $usernameOrEmail);
408 499 }
409 500
410 501 if (!$user_data) {
411 - return $this->sendError([
412 - 'message' => __('Invalid username or email', 'fluent-support')
413 - ]);
502 + return $this->sendResetPassResponse();
414 503 }
415 504
416 505 $user_data = apply_filters('lostpassword_user_data', $user_data, $errors);
417 506
@@ -419,24 +508,17 @@
419 508
420 509 $errors = apply_filters('lostpassword_errors', $errors, $user_data);
421 510
422 511 if ($errors->has_errors()) {
423 - return $this->sendError([
424 - 'message' => $errors->get_error_message()
425 - ]);
512 + return $this->sendResetPassResponse();
426 513 }
427 514
428 515 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 - ]);
516 + return $this->sendResetPassResponse();
432 517 }
433 518
434 519 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 - ]);
520 + return $this->sendResetPassResponse();
439 521 }
440 522
441 523 // Redefining user_login ensures we return the right case in the email.
442 524 $user_login = $user_data->user_login;
@@ -444,21 +526,13 @@
444 526 do_action('retrieve_password', $user_login);
445 527
446 528 $allow = apply_filters('allow_password_reset', true, $user_data->ID);
447 529
448 - if (!$allow) {
449 - return $this->sendError([
450 - 'message' => __('Password reset is not allowed for this user', 'fluent-support')
451 - ]);
530 + if (!$allow || is_wp_error($allow)) {
531 + return $this->sendResetPassResponse();
452 532 }
453 533
454 - if (is_wp_error($allow)) {
455 - return $this->sendError([
456 - 'message' => $allow->get_error_message()
457 - ]);
458 - }
459 534
460 -
461 535 /*
462 536 * Filter reset password link text
463 537 *
464 538 * @since v1.5.7
@@ -463,10 +537,21 @@
463 537 *
464 538 * @since v1.5.7
465 539 * @param string $linkText
466 540 */
541 + // translators: %s is the site name
467 542 $linkText = apply_filters("fluent_support/reset_password_link", sprintf(__('Reset your password for %s', 'fluent-support'), get_bloginfo('name')));
468 543
544 + // get_password_reset_key() invalidates the previous link, so repeated requests could
545 + // block recovery. The owner already has a working link from the first send.
546 + $cooldownKey = 'fs_reset_pass_sent_' . wp_hash($user_data->ID);
547 +
548 + if (get_transient($cooldownKey)) {
549 + return $this->sendResetPassResponse();
550 + }
551 +
552 + set_transient($cooldownKey, 1, 5 * MINUTE_IN_SECONDS);
553 +
469 554 $resetUrl = add_query_arg([
470 555 'action' => 'rp',
471 556 'key' => get_password_reset_key($user_data),
472 557 'login' => rawurlencode($user_data->user_login)
@@ -479,15 +564,18 @@
479 564 *
480 565 * @since v1.5.7
481 566 * @param string $mailSubject
482 567 */
568 + // translators: %s is the site name
483 569 $mailSubject = apply_filters("fluent_support/reset_password_mail_subject", sprintf(__('Reset your password for %s support portal', 'fluent-support'), get_bloginfo('name')));
484 570
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'));
571 + // translators: %s is the user's first name
572 + $message = '<p>' . sprintf(__('Hi %s,', 'fluent-support'), $user_data->first_name) . '</p>' .
573 + '<p>' . __('Someone has requested a new password for the following account on WordPress:', 'fluent-support') . '</p>' .
574 + // translators: %s is the username
575 + '<p>' . sprintf(__('Username: %s', 'fluent-support'), $user_login) . '</p>' .
576 + '<p>' . $resetLink . '</p>' .
577 + '<p>' . __('If you did not request to reset your password, please ignore this email.', 'fluent-support') . '</p>';
490 578
491 579 /*
492 580 * Filter reset password email body text
493 581 *
@@ -501,15 +589,24 @@
501 589 $headers = array('Content-Type: text/html; charset=UTF-8');
502 590
503 591 wp_mail($user_data->user_email, $mailSubject, $message, $headers);
504 592
593 + return $this->sendResetPassResponse();
594 + }
595 +
596 + /**
597 + * Same response for every outcome, so the form can't confirm which accounts exist.
598 + *
599 + * @return mixed
600 + */
601 + protected function sendResetPassResponse()
602 + {
505 603 return $this->sendSuccess([
506 - 'message' => __('Please check your email for the reset link', 'fluent-support')
604 + '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 605 ]);
508 606 }
509 607
510 608 /**
511 - * getMessages message will return the validation message regarding sign up or sign in
512 609 * @param array $rules
513 610 * @return mixed
514 611 */
515 612 protected function getMessages($rules = [])
@@ -524,11 +621,10 @@
524 621 return apply_filters('fluent_support/signup_validation_messages', [], $rules);
525 622 }
526 623
527 624 /**
528 - * createUser method will create new user
529 625 * @param array $formData
530 - * @return mixed
626 + * @return int|false
531 627 */
532 628 public function createUser($formData = [])
533 629 {
534 630 /*
@@ -547,9 +643,9 @@
547 643 */
548 644 $userName = apply_filters('fluent_support/signup_username', Arr::get($formData, 'username'));
549 645
550 646 if (empty($formData['password'])) {
551 - $password = wp_generate_password(8);
647 + $password = wp_generate_password(16, true, true);
552 648 } else {
553 649 $password = $formData['password'];
554 650 }
555 651
@@ -581,22 +677,29 @@
581 677
582 678 }
583 679
584 680 /**
585 - * maybeUpdateUser method will update user information if exists
586 - * @param $userId
587 - * @param $formData
681 + * Sets the name fields from first/last name, if given.
682 + *
683 + * @param int $userId
684 + * @param array $formData
588 685 */
589 686 public function maybeUpdateUser($userId, $formData)
590 687 {
591 - $name = trim(Arr::get($formData, 'first_name') . ' ' . Arr::get($formData, 'last_name'));
688 + if (!$userId) {
689 + return;
690 + }
592 691
692 + $firstName = Arr::get($formData, 'first_name', '');
693 + $lastName = Arr::get($formData, 'last_name', '');
694 + $name = trim($firstName . ' ' . $lastName);
695 +
593 696 $data = array_filter([
594 697 'ID' => $userId,
595 698 'user_nicename' => $name,
596 699 'display_name' => $name,
597 - 'first_name' => Arr::get($formData, 'first_name'),
598 - 'last_name' => Arr::get($formData, 'last_name'),
700 + 'first_name' => $firstName,
701 + 'last_name' => $lastName,
599 702 ]);
600 703
601 704 if ($name) {
602 705 /*
@@ -626,9 +729,9 @@
626 729 }
627 730 }
628 731
629 732 public function addUserMetaData($userId, $formData) {
630 - $customFieldsKey = apply_filters('fluent_support/custom_registration_form_fields_key', Helper::getBusinessSettings('custom_registration_form_field'));
733 + $customFieldsKey = Helper::getCustomRegistrationFieldKeys();
631 734
632 735 if (empty($customFieldsKey)) {
633 736 return;
634 737 }
@@ -633,18 +736,16 @@
633 736 return;
634 737 }
635 738
636 739 foreach ($customFieldsKey as $key) {
637 - if (isset($formData[$key])) {
638 - $fieldValue = $formData[$key];
639 - update_user_meta($userId, $key, $fieldValue);
740 + if (isset($formData[$key]) && is_scalar($formData[$key])) {
741 + update_user_meta($userId, $key, sanitize_text_field($formData[$key]));
640 742 }
641 743 }
642 744 }
643 745
644 746 /**
645 - * assignRole method will assign role to a given user id
646 - * @param $userId
747 + * @param int $userId
647 748 */
648 749 protected function assignRole($userId)
649 750 {
650 751 $user = new \WP_User($userId);
@@ -652,9 +753,9 @@
652 753 /*
653 754 * Action before assigning role to registered user
654 755 *
655 756 * @since v1.0.0
656 - * @param array $data
757 + * @param \WP_User $user
657 758 */
658 759 do_action('fluent_support/before_assigning_role', $user);
659 760 /*
660 761 * Filter user assignable role after signup
@@ -668,9 +769,9 @@
668 769 /*
669 770 * Action after assigning role to registered user
670 771 *
671 772 * @since v1.0.0
672 - * @param array $data
773 + * @param \WP_User $user
673 774 */
674 775 do_action('fluent_support/after_assigning_role', $user);
675 776 }
676 777
@@ -675,10 +776,9 @@
675 776 }
676 777
677 778
678 779 /**
679 - * login method will clear existing cookies and set new cookie for a given user id
680 - * @param $userId
780 + * @param int $userId
681 781 */
682 782 protected function login($userId)
683 783 {
684 784 /*