Как настроить проверку электронной почты пользователя после регистрации?
Знаете, как все эти сайты отправляют ссылки своим новым пользователям для подтверждения их адреса электронной почты? Я пытаюсь настроить нечто подобное, но после некоторых исследований я все еще не нашел хорошего объяснения, как это реализовать.
Я открыт для рекомендаций по плагинам, однако большинство найденных мной плагинов имеют множество других функций, которые мне на самом деле не нужны.
Как можно добавить это в мой код без использования плагина?
Мой подход заключается в том, чтобы добавить атрибут 'Email not verified' в метаданные пользователя после регистрации и отправить письмо с каким-то ключом подтверждения пользователю. Как я могу проверить, действительно ли пользователь перешел по этой ссылке?
Спасибо за любой совет
Вы можете использовать хук user_register
add_action( 'user_register', 'my_registration', 10, 2 );
function my_registration( $user_id ) {
// получаем данные пользователя
$user_info = get_userdata($user_id);
// создаем md5-код для последующей проверки
$code = md5(time());
// формируем код для отправки пользователю по email
$string = array('id'=>$user_id, 'code'=>$code);
// создаем код активации и статус активации
update_user_meta($user_id, 'account_activated', 0);
update_user_meta($user_id, 'activation_code', $code);
// формируем URL
$url = get_site_url(). '/my-account/?act=' .base64_encode( serialize($string));
// здесь можно доработать для более красивого отображения
$html = 'Пожалуйста, перейдите по следующей ссылке <br/><br/> <a href="'.$url.'">'.$url.'</a>';
// отправляем email пользователю
wp_mail( $user_info->user_email, __('Тема письма','text-domain') , $html);
}
Вы можете проверить наличие $_GET['act']
и затем активировать аккаунт, если ключ действителен, обновив мета-значение account_activated
. Для проверки статуса активации при каждой попытке входа пользователя можно использовать хук wp_authenticate_user
.
Код для проверки:
add_action( 'init', 'verify_user_code' );
function verify_user_code(){
if(isset($_GET['act'])){
$data = unserialize(base64_decode($_GET['act']));
$code = get_user_meta($data['id'], 'activation_code', true);
// проверяем, совпадает ли полученный код с нашим
if($code == $data['code']){
// обновляем мета-данные пользователя
update_user_meta($data['id'], 'is_activated', 1);
wc_add_notice( __( '<strong>Успех:</strong> Ваш аккаунт был активирован! ', 'text-domain' ) );
}
}
}

Ребята, я считаю, что приведенный выше метод вводит ненужные уязвимости в систему безопасности.
Основная цель верификации email - убедиться, что пользователи регистрируются с реальными адресами электронной почты, которыми они владеют или хотя бы имеют к ним доступ. Мы не хотим, чтобы люди регистрировались, например, с случайными email-адресами, принадлежащими другим людям, например, с вашим email-адресом.
Приведенный код содержит уязвимости, которые могут позволить хакеру зарегистрировать случайный email, принадлежащий другому человеку, а затем относительно легко подобрать значения $user_id и $code на странице подтверждения email.
Первая уязвимость
Вы используете $user_id. Я понимаю, что это значение может быть чем угодно, но обычно это будет целое число, особенно если используется WordPress (на котором работает около 30% сайтов в интернете), и судя по приведенному PHP-коду, он действительно основан на WordPress. Хакер либо получает свой $user_id в процессе регистрации, либо подбирает его методом перебора, просто перебирая числа последовательно, начиная с 1 и продолжая 2, 3, 4, 5, 6... Они подберут свой $user_id менее чем за день, а может быть, даже менее чем за час, если на вашем сайте не так много участников.
Вторая уязвимость
Вы создаете $code с помощью хеш-функции MD5 и времени регистрации. Хакер знает, в какое время он зарегистрировался. Допустим, хакер регистрируется в 15:00. Теперь все, что нужно сделать хакеру, это получить MD5-хеш для времени с 14:55 до 15:05, и он подберет $code менее чем за час.
Учитывая вышесказанное, хакер может просто подобрать $user_id и $code менее чем за день и подтвердить email-адрес, которым он не владеет
ну-ну-ну
Лучшим подходом было бы генерация $code с помощью функции rand(), используя заглавные (A-Z) и строчные (a-z) буквы, цифры (0-9) и специальные символы, например (!&#). Эта MD5-хеш функция использует только цифры 0-9 и строчные буквы a-f, и ваш способ ее использования на основе времени регистрации делает ее невероятно легкой для сужения диапазона и атаки перебором.
Я написал приведенный ниже PHP-код для генерации случайного $code с заглавными/строчными буквами/цифрами/специальными символами. Не делайте жизнь хакеров такой простой, ребята.
function generateRandomString($stringLength){
//указываем символы, которые будут использоваться для генерации случайной строки, не указывайте символы, которые WordPress не разрешает при создании.
$characters = "0123456789ABCDEFGHIJKLMNPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_[]{}!@$%^*().,>=-;|:?";
//получаем общую длину указанных символов для генерации случайной строки
$charactersLength = strlen($characters);
//объявляем строку, которую будем использовать для создания случайной строки
$randomString = '';
for ($i = 0; $i < $stringLength; $i++) {
//генерируем случайные символы
$randomCharacter = $characters[rand(0, $charactersLength - 1)];
//добавляем случайные символы к случайной строке
$randomString .= $randomCharacter;
};
//sanitize_user, на всякий случай
$sanRandomString = sanitize_user($randomString);
//проверяем, что случайная строка содержит заглавные/строчные буквы/цифры/спецсимволы и имеет правильную длину
if ( (preg_match('([a-zA-Z].*[0-9]|[0-9].*[a-zA-Z].*[_\W])', $sanRandomString)==1) && (strlen($sanRandomString)==$stringLength) )
{
//возвращаем случайную строку, если она соответствует критериям сложности
return $sanRandomString;
} else {
//если случайная строка не соответствует минимальным критериям, вызываем функцию снова
return call_user_func("generateRandomString",($stringLength) );
}
}//конец функции generateRandomString
//вызываем функцию для генерации случайной строки с заглавными/строчными буквами/цифрами/спецсимволами
//в функцию передаем требуемую длину строки, в этом примере будет сгенерирована строка длиной 32 символа
$code = generateRandomString(32);
echo $code;

Отличный ответ! Было бы еще немного лучше, если бы вы немного отформатировали код. Кроме того, символ $ нужно экранировать. Также, поправьте меня, если я ошибаюсь, но нет причин использовать call_user_func
вместо прямого вызова функции.

Причина, по которой я использую call_user_function, заключается в том, что функция создает случайную строку любой указанной длины. Если критерии сложности случайной строки не выполняются, функция автоматически выполнится снова. Например, если функция сгенерировала случайную строку без заглавной буквы или цифры, то call_user_function выполнит функцию снова. Функция вернет случайную строку только тогда, когда будут выполнены все критерии сложности.

Я протестировал использование base_64_encode для $code, сгенерированного функцией случайной строки, прикрепленного к $url, а затем извлечение с помощью метода GET без проблем.

Также очень плохая идея просто отправлять $code напрямую в get_user_meta, так как это то же самое, что хранить пароль в открытом виде в базе данных. Вам следует шифровать $code перед сохранением в get_user_meta. Затем расшифровывать с помощью $code, прикрепленного к $url. В противном случае вы просто обходите всю суть хеширования паролей в безопасности WordPress. Посмотрите на вашу таблицу wp-users - пароли там не хранятся в открытом виде, поэтому вам также нужно хешировать $code для сброса пароля перед сохранением. Используйте функцию password_hash() для шифрования и password_verify() для расшифровки.

Я не думаю, что код активации пользователя обязательно нужно хешировать для обеспечения безопасности. Если кто-то получит доступ к вашей базе данных, коды активации пользователей действительно не имеют значения - они могут просто установить email как подтвержденный, хотя у них, вероятно, будут гораздо худшие намерения. Что касается call_user_function
, я до сих пор не понимаю, почему вы не можете просто вызвать функцию из самой себя. Разве функция не может вызывать саму себя в PHP?

Да, функция вызывается напрямую в приведенном выше коде в конце, где написано "$code = generateRandomString(32);". Функция использует preg_match для проверки, содержит ли сгенерированная случайная строка заглавные буквы/строчные буквы/цифры/специальные символы. Если случайная строка не соответствует критериям сложности, call_user_function автоматически выполняет функцию снова. Функция будет продолжать генерировать случайные строки до тех пор, пока одна из них не будет соответствовать критериям сложности, указанным в preg_match. Первая случайная строка может быть "abcdefghijklmnopqrstuvwxyzabcdef"

Строка "abcdefghijklmnopqrstuvwxyzabcdef" не соответствует требованиям сложности, так как не содержит цифр, заглавных букв или специальных символов, поэтому функция call_user_function автоматически вызывается повторно, пока не будет получена случайная строка, удовлетворяющая требованиям сложности. Это работает как цикл.

Мне интересно, почему, если WordPress хэширует пароль учетной записи в базе данных, ключ сброса для этого самого пароля также не хэшируется?

Как вы считаете, должен ли WordPress хэшировать пароли учетных записей в базе данных?
