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 +41 -32 2.6.02.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 {
@@ -258,9 +270,12 @@
258 270 }
259 271
260 272 public static function isTwoFactorEnabled()
261 273 {
262 - return apply_filters('fluent_auth/verify_signup_email', true);
274 + // fluent_auth/verify_signup_email is kept for backward compatibility with FluentAuth-targeted snippets
275 + $enabled = apply_filters('fluent_auth/verify_signup_email', true);
276 +
277 + return apply_filters('fluent_community/auth/two_factor_enabled', $enabled);
263 278 }
264 279
265 280 public static function get2FaRegistrationCodeForm($formData)
266 281 {
@@ -265,28 +280,24 @@
265 280 public static function get2FaRegistrationCodeForm($formData)
266 281 {
267 282 $generalSettings = Helper::generalSettings();
268 283 try {
269 - $verifcationCode = str_pad(random_int(100123, 900987), 6, 0, STR_PAD_LEFT);
284 + $verifcationCode = str_pad((string) random_int(100123, 900987), 6, '0', STR_PAD_LEFT);
270 285 } catch (\Exception $e) {
271 - $verifcationCode = str_pad(wp_rand(100123, 900987), 6, 0, STR_PAD_LEFT);
286 + $verifcationCode = str_pad((string) wp_rand(100123, 900987), 6, '0', STR_PAD_LEFT);
272 287 }
273 288
274 - // 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.
275 291 $codeHash = wp_hash_password($verifcationCode);
276 -
277 - // Create a token with the email and code hash
278 - $data = [
292 + $signedToken = 'fcs_' . wp_generate_password(40, false);
293 + set_transient('fcom_signup_2fa_' . $signedToken, [
279 294 'email' => $formData['email'],
280 295 'code_hash' => $codeHash,
281 - 'expires' => time() + 600 // 10 minutes expiry
282 - ];
283 - $token = base64_encode(json_encode($data));
296 + 'expires' => time() + 600, // 10 minutes expiry
297 + 'attempts' => 0,
298 + ], 600);
284 299
285 - // Sign the token
286 - $signature = hash_hmac('sha256', $token, SECURE_AUTH_KEY);
287 - $signedToken = $token . '.' . $signature;
288 -
289 300 /* translators: %s is replaced by the title of the site */
290 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')));
291 302
292 303 $pStart = '<p style="font-family: Arial, sans-serif; font-size: 16px; font-weight: normal; margin: 0; margin-bottom: 16px;">';
@@ -366,34 +377,21 @@
366 377 }
367 378
368 379 public static function validateVerificationCode($code, $verificationToken, $formData)
369 380 {
370 - if (!is_string($verificationToken) || strpos($verificationToken, '.') === false) {
381 + if (!is_string($verificationToken) || $verificationToken === '') {
371 382 return new \WP_Error('invalid_token', __('Invalid verification token. Please try again', 'fluent-community'));
372 383 }
373 384
374 - list($data, $signature) = explode('.', $verificationToken, 2);
375 - if (!$data || !$signature) {
376 - return new \WP_Error('invalid_token', __('Invalid verification token. Please try again', 'fluent-community'));
377 - }
385 + $transientKey = 'fcom_signup_2fa_' . $verificationToken;
386 + $data = get_transient($transientKey);
378 387
379 - $expectedSignature = hash_hmac('sha256', $data, SECURE_AUTH_KEY);
380 -
381 - if (!hash_equals($expectedSignature, $signature)) {
382 - return new \WP_Error('invalid_token', __('Invalid verification token. Please try again', 'fluent-community'));
383 - }
384 -
385 - $decodedData = base64_decode($data, true); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
386 - if ($decodedData === false) {
387 - return new \WP_Error('invalid_token', __('Invalid verification token. Please try again', 'fluent-community'));
388 - }
389 -
390 - $data = json_decode($decodedData, true);
391 388 if (!is_array($data) || empty($data['expires']) || empty($data['email']) || empty($data['code_hash'])) {
392 389 return new \WP_Error('invalid_token', __('Invalid verification token. Please try again', 'fluent-community'));
393 390 }
394 391
395 392 if ((int)$data['expires'] < time()) {
393 + delete_transient($transientKey);
396 394 return new \WP_Error('expired_token', __('Verification token has expired. Please try again.', 'fluent-community'));
397 395 }
398 396
399 397 if (!isset($formData['email']) || $data['email'] !== $formData['email']) {
@@ -399,11 +397,22 @@
399 397 if (!isset($formData['email']) || $data['email'] !== $formData['email']) {
400 398 return new \WP_Error('invalid_email', __('Invalid email address. Please try again', 'fluent-community'));
401 399 }
402 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 +
403 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()));
404 410 return new \WP_Error('invalid_code', __('Invalid verification code. Please try again', 'fluent-community'));
405 411 }
412 +
413 + // Single-use: consume the challenge on success.
414 + delete_transient($transientKey);
406 415
407 416 return true;
408 417 }
409 418