PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.10.01
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.10.01
2.10.0 2.10.01 2.9.1 2.9.0 2.8.1 2.8.0 2.7.7 2.7.5 2.7.0 2.6.01 2.6.0 2.5.0 2.4.01 trunk 1.0.90 1.0.91 1.0.92 1.0.93 1.0.94 1.0.95 1.0.96 1.0.97 1.0.98 1.0.99 1.1.0 All 77 releases
← All changes | Modules/Auth/AuthHelper.php +35 -29 2.7.72.10.01 View file →
@@ -45,10 +45,20 @@
45 45 __('<strong>Error:</strong> This email address is already registered. Please login or try resetting your password.', 'fluent-community')
46 46 );
47 47 }
48 48
49 + /**
50 + * MemberPress rejects every `register_post` while its "Disable WordPress registration form"
51 + * option is on (default on). That option targets wp-login.php, not the community portal, which has its own registration gate.
52 + */
53 + $hadMeprBlocker = remove_action('register_post', 'MeprUsersCtrl::maybe_disable_wp_registration_form', 10);
54 +
49 55 do_action('register_post', $sanitized_user_login, $user_email, $errors); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
50 56
57 + if ($hadMeprBlocker) {
58 + add_action('register_post', 'MeprUsersCtrl::maybe_disable_wp_registration_form', 10, 3);
59 + }
60 +
51 61 if ($errors->has_errors()) {
52 62 return $errors;
53 63 }
54 64
@@ -240,9 +250,11 @@
240 250 }
241 251
242 252 public static function isPasswordConfRequired()
243 253 {
244 - return apply_filters('fluent_community/autg/password_confirmation', true);
254 + $isRequired = apply_filters_deprecated('fluent_community/autg/password_confirmation', [true], '2.7.8', 'fluent_community/auth/password_confirmation');
255 +
256 + return apply_filters('fluent_community/auth/password_confirmation', $isRequired);
245 257 }
246 258
247 259 public static function isRegistrationEnabled()
248 260 {
@@ -273,23 +285,19 @@
273 285 } catch (\Exception $e) {
274 286 $verifcationCode = str_pad((string) wp_rand(100123, 900987), 6, '0', STR_PAD_LEFT);
275 287 }
276 288
277 - // Hash the code
289 + // Keep the code hash server-side, keyed by an opaque challenge id. The client only ever
290 + // receives the id, never the password verifier, so the code cannot be recovered offline.
278 291 $codeHash = wp_hash_password($verifcationCode);
279 -
280 - // Create a token with the email and code hash
281 - $data = [
292 + $signedToken = 'fcs_' . wp_generate_password(40, false);
293 + set_transient('fcom_signup_2fa_' . $signedToken, [
282 294 'email' => $formData['email'],
283 295 'code_hash' => $codeHash,
284 - 'expires' => time() + 600 // 10 minutes expiry
285 - ];
286 - $token = base64_encode(json_encode($data));
296 + 'expires' => time() + 600, // 10 minutes expiry
297 + 'attempts' => 0,
298 + ], 600);
287 299
288 - // Sign the token
289 - $signature = hash_hmac('sha256', $token, SECURE_AUTH_KEY);
290 - $signedToken = $token . '.' . $signature;
291 -
292 300 /* translators: %s is replaced by the title of the site */
293 301 $mailSubject = apply_filters("fluent_community/auth/signup_verification_mail_subject", sprintf(__('Your registration verification code for %s', 'fluent-community'), Arr::get($generalSettings, 'site_title')));
294 302
295 303 $pStart = '<p style="font-family: Arial, sans-serif; font-size: 16px; font-weight: normal; margin: 0; margin-bottom: 16px;">';
@@ -369,34 +377,21 @@
369 377 }
370 378
371 379 public static function validateVerificationCode($code, $verificationToken, $formData)
372 380 {
373 - if (!is_string($verificationToken) || strpos($verificationToken, '.') === false) {
381 + if (!is_string($verificationToken) || $verificationToken === '') {
374 382 return new \WP_Error('invalid_token', __('Invalid verification token. Please try again', 'fluent-community'));
375 383 }
376 384
377 - list($data, $signature) = explode('.', $verificationToken, 2);
378 - if (!$data || !$signature) {
379 - return new \WP_Error('invalid_token', __('Invalid verification token. Please try again', 'fluent-community'));
380 - }
385 + $transientKey = 'fcom_signup_2fa_' . $verificationToken;
386 + $data = get_transient($transientKey);
381 387
382 - $expectedSignature = hash_hmac('sha256', $data, SECURE_AUTH_KEY);
383 -
384 - if (!hash_equals($expectedSignature, $signature)) {
385 - return new \WP_Error('invalid_token', __('Invalid verification token. Please try again', 'fluent-community'));
386 - }
387 -
388 - $decodedData = base64_decode($data, true); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
389 - if ($decodedData === false) {
390 - return new \WP_Error('invalid_token', __('Invalid verification token. Please try again', 'fluent-community'));
391 - }
392 -
393 - $data = json_decode($decodedData, true);
394 388 if (!is_array($data) || empty($data['expires']) || empty($data['email']) || empty($data['code_hash'])) {
395 389 return new \WP_Error('invalid_token', __('Invalid verification token. Please try again', 'fluent-community'));
396 390 }
397 391
398 392 if ((int)$data['expires'] < time()) {
393 + delete_transient($transientKey);
399 394 return new \WP_Error('expired_token', __('Verification token has expired. Please try again.', 'fluent-community'));
400 395 }
401 396
402 397 if (!isset($formData['email']) || $data['email'] !== $formData['email']) {
@@ -402,11 +397,22 @@
402 397 if (!isset($formData['email']) || $data['email'] !== $formData['email']) {
403 398 return new \WP_Error('invalid_email', __('Invalid email address. Please try again', 'fluent-community'));
404 399 }
405 400
401 + // Cap online guesses per challenge: after too many wrong codes the challenge is burned.
402 + if ((int) Arr::get($data, 'attempts', 0) >= 10) {
403 + delete_transient($transientKey);
404 + return new \WP_Error('too_many_attempts', __('Too many invalid attempts. Please try again', 'fluent-community'));
405 + }
406 +
406 407 if (!wp_check_password($code, $data['code_hash'])) {
408 + $data['attempts'] = (int) Arr::get($data, 'attempts', 0) + 1;
409 + set_transient($transientKey, $data, max(1, (int) $data['expires'] - time()));
407 410 return new \WP_Error('invalid_code', __('Invalid verification code. Please try again', 'fluent-community'));
408 411 }
412 +
413 + // Single-use: consume the challenge on success.
414 + delete_transient($transientKey);
409 415
410 416 return true;
411 417 }
412 418