PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / trunk
Fluent Support – Helpdesk & Customer Support Ticket System vtrunk
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
fluent-support / app / Hooks / Handlers / EmailVerificationHandler.php

EmailVerificationHandler.php in Fluent Support – Helpdesk & Customer Support Ticket System trunk, at app/Hooks/Handlers/EmailVerificationHandler.php

142 lines 6.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentSupport\App\Hooks\Handlers;
4
5 use FluentSupport\App\Models\Meta;
6 use FluentSupport\App\Services\Helper;
7 use FluentSupport\Framework\Support\Arr;
8
9
10 class EmailVerificationHandler
11 {
12 /**
13 * How long an issued signup verification code stays valid, in seconds.
14 * Matches the "valid for 10 minutes" copy in the verification email below.
15 */
16 const CODE_TTL_SECONDS = 10 * MINUTE_IN_SECONDS;
17
18 public static function sendSignupEmailVerificationHtml($formData)
19 {
20 $email = strtolower(trim($formData['email']));
21
22 // IP bucket is a generous volumetric backstop (shared office/NAT IPs can have
23 // many unrelated signups); the email bucket is the primary throttle, since this
24 // endpoint is otherwise unauthenticated and can be used to mail-bomb any address.
25 $ipKey = 'fs_signup_verify_ip_' . wp_hash(Helper::getIp());
26 $emailKey = 'fs_signup_verify_email_' . wp_hash($email);
27
28 /*
29 * Site-wide ceiling on signup verification mail, as a circuit breaker against a
30 * distributed attacker who rotates both source IP and target address and so never
31 * trips either bucket above. Deliberately generous: this is the one bucket a
32 * legitimate signup rush shares, and tripping it turns signup off for everyone,
33 * so it is sized to be unreachable by organic traffic and filterable for sites
34 * that genuinely run hotter.
35 *
36 * @since v2.2.2
37 * @param int $limit Signup verification emails allowed site-wide per hour.
38 */
39 $globalLimit = (int) apply_filters('fluent_support/signup_verification_hourly_limit', 100);
40
41 // Order matters: PHP short-circuits, so the global counter is only incremented by
42 // requests that already cleared the IP and email gates. Were it checked first, a
43 // flood from a single IP would burn the site-wide budget with requests that are
44 // about to be rejected anyway, handing an attacker a cheap way to trip the breaker
45 // and lock out every legitimate signup.
46 if (Helper::hitRateLimit($ipKey, 20)
47 || Helper::hitRateLimit($emailKey, 5)
48 || Helper::hitRateLimit('fs_signup_verify_global', $globalLimit, HOUR_IN_SECONDS)
49 ) {
50 wp_send_json([
51 'message' => __('Too many verification code requests. Please try again later.', 'fluent-support')
52 ], 429);
53 }
54
55 try {
56 $verifcationCode = str_pad(random_int(100123, 900987), 6, 0, STR_PAD_LEFT);
57 } catch (\Exception $e) {
58 $verifcationCode = str_pad(wp_rand(100123, 900987), 6, 0, STR_PAD_LEFT);
59 }
60
61 $string = $formData['email'] . '-' . wp_generate_uuid4() . wp_rand(1, 99999999);
62 $hash = wp_hash_password($string);
63 $hash = sanitize_title($hash, '', 'display');
64 $hash .= $formData['email'] . '-' . time();
65
66 $data = array(
67 'login_hash' => $hash,
68 'status' => 'issued',
69 'email' => strtolower(trim($formData['email'])),
70 'ip_address' => Helper::getIp(),
71 'use_type' => 'signup_verification',
72 'used_count' => 0,
73 'two_fa_code_hash' => wp_hash_password($verifcationCode),
74 'valid_till' => gmdate('Y-m-d H:i:s', current_time('timestamp') + self::CODE_TTL_SECONDS),
75 'created_at' => current_time('mysql'),
76 'updated_at' => current_time('mysql')
77 );
78
79 $savedRecord = Meta::create([
80 'object_type' => 'fs_login_hashes',
81 'key' => $hash,
82 'value' => maybe_serialize($data)
83 ]);
84
85 if (!$savedRecord || !$savedRecord->exists) {
86 wp_send_json([
87 'message' => __('Unable to send verification code. Please try again later.', 'fluent-support')
88 ], 500);
89 }
90
91 // translators: %s is the site name
92 $mailSubject = apply_filters("fluent_support/signup_verification_mail_subject", sprintf(__('Your registration verification code for %s', 'fluent-support'), get_bloginfo('name')));
93
94 $pStart = '<p style="font-family: Arial, sans-serif; font-size: 16px; font-weight: normal; margin: 0; margin-bottom: 16px;">';
95
96 // translators: %s is the user's first name
97 $message = $pStart . sprintf(__('Hello %s,', 'fluent-support'), Arr::get($formData, 'first_name')) . '</p>' .
98 $pStart . __('Thank you for registering with us! To complete the setup of your account, please enter the verification code below on the registration page.', 'fluent-support') . '</p>' .
99 // translators: %s is the verification code
100 $pStart . '<b>' . sprintf(__('Verification Code: %s', 'fluent-support'), $verifcationCode) . '</b></p>' .
101 '<br />' .
102 $pStart . __('This code is valid for 10 minutes and is meant to ensure the security of your account. If you did not initiate this request, please ignore this email.', 'fluent-support') . '</p>';
103
104 $message = apply_filters('fluent_support/signup_verification_email_body', $message, $verifcationCode, $formData);
105
106 $data = [
107 'body' => $message,
108 'pre_header' => __('Activate your account', 'fluent-support'),
109 'show_footer' => false
110 ];
111
112 $message = Helper::loadView('notification', $data);
113 $headers = array('Content-Type: text/html; charset=UTF-8');
114
115 \wp_mail($formData['email'], $mailSubject, $message, $headers);
116
117 ob_start();
118 ?>
119 <div class="fs_signup_verification">
120 <div class="fs_field_group fs_field_verification">
121 <?php // translators: %s is the email address ?>
122 <p><?php echo esc_html(sprintf(__('A verification code has been sent to %s. Please provide the code below:', 'fluent-support'), $formData['email'])); ?></p>
123 <input type="hidden" name="_email_verification_hash" value="<?php echo esc_attr($hash); ?>"/>
124 <div class="fs_field_label is-required">
125 <label for="fs_field_verification"><?php esc_html_e('Verification Code', 'fluent-support'); ?></label>
126 </div>
127 <div class="fs_input_wrap">
128 <input type="text" id="fs_field_verification" placeholder="" name="_email_verification_token" required>
129 </div>
130 </div>
131 <button
132 style="display: inline-block; cursor: pointer; border: 0; background: #2271b1; color: #fff; text-decoration: none; text-shadow: none; min-height: 32px; padding: 8px 24px; font-size: 14px; border-radius: 3px; margin-top: 10px;"
133 id="fs_verification_submit" type="submit">
134 <?php esc_html_e('Complete Signup', 'fluent-support'); ?>
135 </button>
136 </div>
137 <?php
138 return ob_get_clean();
139 }
140
141 }
142