514 lines
16 KiB
PHP
514 lines
16 KiB
PHP
<?php
|
||
/**
|
||
* @package AKSRT
|
||
*/
|
||
|
||
namespace AKSRT\Auth;
|
||
|
||
defined('ABSPATH') || exit;
|
||
|
||
trait ServicePart1b {
|
||
|
||
public static function admin_send_verification(int $user_id, int $by_admin_id = 0): array {
|
||
return EmailVerification::admin_send_verification($user_id, $by_admin_id);
|
||
}
|
||
|
||
public static function backfill_legacy_users_as_verified(bool $force = false): int {
|
||
return EmailVerification::backfill_legacy_users_as_verified($force);
|
||
}
|
||
|
||
public static function run_async_backfill_batch(int $offset = 0): void {
|
||
EmailVerification::run_async_backfill_batch($offset);
|
||
}
|
||
|
||
private static function delete_user_account(int $user_id): void {
|
||
if ($user_id <= 0) {
|
||
return;
|
||
}
|
||
if (!function_exists('wp_delete_user')) {
|
||
require_once ABSPATH . 'wp-admin/includes/user.php';
|
||
}
|
||
if (function_exists('wp_delete_user')) {
|
||
wp_delete_user($user_id);
|
||
}
|
||
}
|
||
|
||
private static function oauth_email_is_verified(array $oauth_data): bool {
|
||
$user_data = isset($oauth_data['user_data']) && is_array($oauth_data['user_data']) ? $oauth_data['user_data'] : $oauth_data;
|
||
if (empty($user_data['email'])) {
|
||
return false;
|
||
}
|
||
|
||
if (!empty($user_data['email_verified'])) {
|
||
return true;
|
||
}
|
||
|
||
$provider = sanitize_key((string) ($user_data['provider'] ?? $oauth_data['provider'] ?? ''));
|
||
if ($provider === 'google' && !empty($user_data['email'])) {
|
||
return true;
|
||
}
|
||
|
||
if ($provider === 'github' && isset($user_data['email_verified'])) {
|
||
return !empty($user_data['email_verified']);
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private static function begin_email_verification(\WP_User $user, string $method, array $args = []): array {
|
||
return EmailVerification::begin_email_verification($user, $method, $args);
|
||
}
|
||
|
||
private static function verify_email_token(int $user_id, string $token): array {
|
||
return EmailVerification::verify_email_token($user_id, $token);
|
||
}
|
||
|
||
private static function verify_email_code(int $user_id, string $code): array {
|
||
return EmailVerification::verify_email_code($user_id, $code);
|
||
}
|
||
|
||
public static function get_reset_context(array $query): array {
|
||
if (!empty($query['key']) || !empty($query['login']) || !empty($query['legacy'])) {
|
||
return [
|
||
'mode' => 'invalid',
|
||
'user' => null,
|
||
'error' => __('旧的重置密码链接已失效,请重新申请重置密码。', 'aksrt'),
|
||
'token' => '',
|
||
'user_id'=> 0,
|
||
];
|
||
}
|
||
|
||
$token = sanitize_text_field($query['token'] ?? '');
|
||
$user_id = isset($query['uid']) ? absint($query['uid']) : 0;
|
||
|
||
if ($token === '' || $user_id <= 0) {
|
||
return [
|
||
'mode' => 'request',
|
||
'user' => null,
|
||
'error' => '',
|
||
'token' => '',
|
||
'user_id'=> 0,
|
||
];
|
||
}
|
||
|
||
$user = self::validate_reset_token($user_id, $token);
|
||
if (is_wp_error($user)) {
|
||
return [
|
||
'mode' => 'invalid',
|
||
'user' => null,
|
||
'error' => $user->get_error_message(),
|
||
'token' => $token,
|
||
'user_id'=> $user_id,
|
||
];
|
||
}
|
||
|
||
return [
|
||
'mode' => 'reset',
|
||
'user' => $user,
|
||
'error' => '',
|
||
'token' => $token,
|
||
'user_id'=> $user_id,
|
||
];
|
||
}
|
||
|
||
public static function request_password_reset(array $post): array {
|
||
if (!wp_verify_nonce($post['_reset_nonce'] ?? '', 'aksrt_reset_password')) {
|
||
return ['success' => false, 'error' => __('安全验证失败,请刷新页面重试', 'aksrt')];
|
||
}
|
||
|
||
if (function_exists('aksrt_require_captcha')) {
|
||
$captcha_result = aksrt_require_captcha('lostpassword', $post);
|
||
if ($captcha_result !== true) {
|
||
return ['success' => false, 'error' => $captcha_result];
|
||
}
|
||
}
|
||
|
||
$raw_login = trim((string) ($post['user_login'] ?? ''));
|
||
if ($raw_login === '') {
|
||
return ['success' => false, 'error' => __('请输入邮箱地址或用户名', 'aksrt')];
|
||
}
|
||
|
||
$user = null;
|
||
if (is_email($raw_login)) {
|
||
$user = get_user_by('email', $raw_login);
|
||
} else {
|
||
$user = get_user_by('login', sanitize_user($raw_login, true));
|
||
}
|
||
|
||
if (!$user || is_wp_error($user)) {
|
||
return ['success' => true, 'message' => __('如果该账号存在,重置邮件已发送到注册邮箱', 'aksrt')];
|
||
}
|
||
|
||
$token = self::create_reset_token($user->ID);
|
||
$sent = self::send_password_reset_email($user, $token);
|
||
|
||
if (!$sent) {
|
||
return ['success' => false, 'error' => __('重置邮件发送失败,请稍后再试', 'aksrt')];
|
||
}
|
||
|
||
return ['success' => true, 'message' => __('如果该账号存在,重置邮件已发送到注册邮箱', 'aksrt')];
|
||
}
|
||
|
||
public static function reset_password_with_token(array $post): array {
|
||
if (!wp_verify_nonce($post['_setpw_nonce'] ?? '', 'aksrt_set_password')) {
|
||
return ['success' => false, 'error' => __('安全验证失败,请刷新页面重试', 'aksrt')];
|
||
}
|
||
|
||
$token = sanitize_text_field($post['reset_token'] ?? '');
|
||
$user_id = isset($post['reset_uid']) ? absint($post['reset_uid']) : 0;
|
||
$pass1 = (string) ($post['pass1'] ?? '');
|
||
$pass2 = (string) ($post['pass2'] ?? '');
|
||
|
||
$user = self::validate_reset_token($user_id, $token);
|
||
if (is_wp_error($user)) {
|
||
return ['success' => false, 'error' => $user->get_error_message(), 'mode' => 'invalid'];
|
||
}
|
||
|
||
$password_check = self::validate_password($pass1, $pass2);
|
||
if ($password_check !== true) {
|
||
return ['success' => false, 'error' => $password_check, 'mode' => 'reset'];
|
||
}
|
||
|
||
wp_set_password($pass1, $user_id);
|
||
self::clear_reset_token($user_id);
|
||
|
||
return ['success' => true, 'message' => __('密码已设置成功!', 'aksrt')];
|
||
}
|
||
|
||
private static function create_reset_token(int $user_id): string {
|
||
$token = wp_generate_password(48, false, false);
|
||
update_user_meta($user_id, self::RESET_TOKEN_HASH_META, hash('sha256', $token));
|
||
update_user_meta($user_id, self::RESET_TOKEN_EXPIRES_META, time() + self::RESET_TOKEN_TTL);
|
||
update_user_meta($user_id, self::RESET_TOKEN_REQUESTED_AT, time());
|
||
return $token;
|
||
}
|
||
|
||
private static function validate_reset_token(int $user_id, string $token) {
|
||
if ($user_id <= 0 || $token === '') {
|
||
return new \WP_Error('invalid_reset_token', __('重置链接无效或已过期', 'aksrt'));
|
||
}
|
||
|
||
$user = get_userdata($user_id);
|
||
if (!$user || is_wp_error($user)) {
|
||
return new \WP_Error('invalid_reset_token', __('重置链接无效或已过期', 'aksrt'));
|
||
}
|
||
|
||
$stored_hash = (string) get_user_meta($user_id, self::RESET_TOKEN_HASH_META, true);
|
||
$expires_at = (int) get_user_meta($user_id, self::RESET_TOKEN_EXPIRES_META, true);
|
||
$token_hash = hash('sha256', $token);
|
||
|
||
if ($stored_hash === '' || $expires_at <= time() || !hash_equals($stored_hash, $token_hash)) {
|
||
return new \WP_Error('invalid_reset_token', __('重置链接无效或已过期', 'aksrt'));
|
||
}
|
||
|
||
return $user;
|
||
}
|
||
|
||
private static function clear_reset_token(int $user_id): void {
|
||
delete_user_meta($user_id, self::RESET_TOKEN_HASH_META);
|
||
delete_user_meta($user_id, self::RESET_TOKEN_EXPIRES_META);
|
||
delete_user_meta($user_id, self::RESET_TOKEN_REQUESTED_AT);
|
||
}
|
||
|
||
private static function send_password_reset_email(\WP_User $user, string $token): bool {
|
||
$reset_url = aksrt_get_reset_password_url($token, $user->ID);
|
||
$site_name = get_bloginfo('name');
|
||
$default_subject = '[' . $site_name . '] ' . __('重置密码', 'aksrt');
|
||
$default_content = sprintf(
|
||
__("您好 %s,\n\n有人请求重置您在 %s 的密码。\n\n请点击以下链接设置新密码:\n\n%s\n\n如果这不是您本人的操作,请忽略此邮件,您的密码不会被更改。", 'aksrt'),
|
||
$user->user_login, $site_name, $reset_url
|
||
);
|
||
|
||
return \AKSRT\Settings\Settings::send_event_email(
|
||
'password_reset', $user->user_email, $default_subject, $default_content,
|
||
['{username}' => $user->user_login, '{site_name}' => $site_name, '{url}' => $reset_url]
|
||
);
|
||
}
|
||
|
||
/* =================================================================
|
||
* 手机号认证(注册 / 登录 / 绑定)
|
||
* 对齐 zibll 的 signup_captcha / signin_captcha / bind_phone_captcha
|
||
* ================================================================= */
|
||
|
||
/**
|
||
* 手机号注册
|
||
*
|
||
* @param array $post POST 数据 (phone, code, password, ref)
|
||
* @return array{success: bool, user_id?: int, error?: string, redirect?: string}
|
||
*/
|
||
public static function register_by_phone(array $post): array {
|
||
$phone = sanitize_text_field((string) ($post['phone'] ?? ''));
|
||
$code = sanitize_text_field((string) ($post['code'] ?? ''));
|
||
$pass = (string) ($post['password'] ?? '');
|
||
|
||
if (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
|
||
return ['success' => false, 'error' => __('手机号格式不正确', 'aksrt')];
|
||
}
|
||
if (empty($code)) {
|
||
return ['success' => false, 'error' => __('请输入验证码', 'aksrt')];
|
||
}
|
||
if (strlen($pass) < 6) {
|
||
return ['success' => false, 'error' => __('密码至少 6 位', 'aksrt')];
|
||
}
|
||
|
||
if (function_exists('aksrt_require_captcha')) {
|
||
$captcha_result = aksrt_require_captcha('register', $post);
|
||
if ($captcha_result !== true) {
|
||
return ['success' => false, 'error' => $captcha_result];
|
||
}
|
||
}
|
||
|
||
// 验证 SMS 验证码
|
||
if (class_exists('\AKSRT\Sms\SMS')) {
|
||
$verify_result = \AKSRT\Sms\SMS::get_instance()->verify_code($phone, $code);
|
||
if (empty($verify_result['success'])) {
|
||
return ['success' => false, 'error' => $verify_result['message'] ?? __('验证码不正确', 'aksrt')];
|
||
}
|
||
} else {
|
||
return ['success' => false, 'error' => __('短信服务不可用', 'aksrt')];
|
||
}
|
||
|
||
// 检查手机号是否已注册
|
||
$existing_user = self::get_user_by_phone($phone);
|
||
if ($existing_user) {
|
||
return ['success' => false, 'error' => __('该手机号已注册', 'aksrt')];
|
||
}
|
||
|
||
// 生成用户名(phone_ + 后4位)
|
||
$username = 'phone_' . substr($phone, -4);
|
||
$suffix = 1;
|
||
$base = $username;
|
||
while (username_exists($username)) {
|
||
$username = $base . '_' . $suffix;
|
||
$suffix++;
|
||
}
|
||
|
||
$ref_service = class_exists('\AKSRT\Payment\ReferralAttribution')
|
||
? \AKSRT\Payment\ReferralAttribution::get_instance()
|
||
: null;
|
||
if ($ref_service) {
|
||
$ref_service->clear_registration_context();
|
||
}
|
||
$registration = class_exists('\AKSRT\Settings\Settings')
|
||
? \AKSRT\Settings\Settings::get_referral_registration_settings()
|
||
: ['enabled' => false, 'register_field' => 'hidden', 'reject_invalid' => true];
|
||
$submitted_ref = sanitize_key((string) ($post['ref'] ?? ''));
|
||
$field_mode = !empty($registration['enabled'])
|
||
? (string) ($registration['register_field'] ?? 'hidden')
|
||
: 'hidden';
|
||
$accept_submitted = !empty($registration['enabled']) && $field_mode !== 'hidden';
|
||
$ref_context = $ref_service
|
||
? $ref_service->prepare_registration($accept_submitted ? $submitted_ref : '')
|
||
: ['valid' => false, 'submitted_valid' => false, 'conflict' => false, 'source' => 'none'];
|
||
if ($field_mode === 'required' && empty($ref_context['valid'])) {
|
||
if ($submitted_ref === '') {
|
||
if ($ref_service) {
|
||
$ref_service->clear_registration_context();
|
||
}
|
||
return ['success' => false, 'error' => __('请填写推广码', 'aksrt')];
|
||
}
|
||
if (!empty($registration['reject_invalid'])) {
|
||
if ($ref_service) {
|
||
$ref_service->clear_registration_context();
|
||
}
|
||
return ['success' => false, 'error' => __('推广码无效,请检查后重试', 'aksrt')];
|
||
}
|
||
}
|
||
|
||
// 创建用户
|
||
$user_id = wp_create_user($username, $pass, $phone . '@sms.aksrt.local');
|
||
if (is_wp_error($user_id)) {
|
||
if ($ref_service) {
|
||
$ref_service->clear_registration_context();
|
||
}
|
||
return ['success' => false, 'error' => $user_id->get_error_message()];
|
||
}
|
||
|
||
// 绑定手机号
|
||
update_user_meta($user_id, 'aksrt_phone', $phone);
|
||
update_user_meta($user_id, 'aksrt_phone_verified', '1');
|
||
|
||
// 邮箱标记为已验证(手机注册等同于已验证)
|
||
update_user_meta($user_id, 'aksrt_email_verified', '1');
|
||
|
||
if ($ref_service) {
|
||
$ref_service->reward_registered_user((int) $user_id);
|
||
}
|
||
|
||
// 自动登录
|
||
wp_set_current_user($user_id);
|
||
wp_set_auth_cookie($user_id, true);
|
||
|
||
$redirect = function_exists('aksrt_get_user_center_url')
|
||
? aksrt_get_user_center_url()
|
||
: home_url('/');
|
||
|
||
return [
|
||
'success' => true,
|
||
'user_id' => $user_id,
|
||
'redirect' => $redirect,
|
||
'message' => __('注册成功', 'aksrt'),
|
||
'notice' => $ref_service ? $ref_service->take_registration_notice() : '',
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 手机号登录(密码登录)
|
||
*
|
||
* @param array $post (phone, password)
|
||
* @return array
|
||
*/
|
||
public static function login_by_phone_password(array $post): array {
|
||
$phone = sanitize_text_field((string) ($post['phone'] ?? ''));
|
||
$pass = (string) ($post['password'] ?? '');
|
||
|
||
if (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
|
||
return ['success' => false, 'error' => __('手机号格式不正确', 'aksrt')];
|
||
}
|
||
|
||
if (function_exists('aksrt_require_captcha')) {
|
||
$captcha_result = aksrt_require_captcha('login', $post);
|
||
if ($captcha_result !== true) {
|
||
return ['success' => false, 'error' => $captcha_result];
|
||
}
|
||
}
|
||
|
||
$user = self::get_user_by_phone($phone);
|
||
if (!$user) {
|
||
return ['success' => false, 'error' => __('该手机号未注册', 'aksrt')];
|
||
}
|
||
|
||
// 验证密码
|
||
if (!wp_check_password($pass, $user->data->user_pass, $user->ID)) {
|
||
return ['success' => false, 'error' => __('密码不正确', 'aksrt')];
|
||
}
|
||
|
||
wp_set_current_user($user->ID);
|
||
wp_set_auth_cookie($user->ID, true);
|
||
|
||
$redirect = function_exists('aksrt_get_user_center_url')
|
||
? aksrt_get_user_center_url()
|
||
: home_url('/');
|
||
|
||
return [
|
||
'success' => true,
|
||
'user_id' => $user->ID,
|
||
'redirect' => $redirect,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 手机号免密登录(SMS 验证码登录)
|
||
*
|
||
* @param array $post (phone, code)
|
||
* @return array
|
||
*/
|
||
public static function login_by_phone_code(array $post): array {
|
||
$phone = sanitize_text_field((string) ($post['phone'] ?? ''));
|
||
$code = sanitize_text_field((string) ($post['code'] ?? ''));
|
||
|
||
if (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
|
||
return ['success' => false, 'error' => __('手机号格式不正确', 'aksrt')];
|
||
}
|
||
|
||
if (function_exists('aksrt_require_captcha')) {
|
||
$captcha_result = aksrt_require_captcha('login', $post);
|
||
if ($captcha_result !== true) {
|
||
return ['success' => false, 'error' => $captcha_result];
|
||
}
|
||
}
|
||
|
||
// 验证 SMS 验证码
|
||
if (class_exists('\AKSRT\Sms\SMS')) {
|
||
$verify_result = \AKSRT\Sms\SMS::get_instance()->verify_code($phone, $code);
|
||
if (empty($verify_result['success'])) {
|
||
return ['success' => false, 'error' => $verify_result['message'] ?? __('验证码不正确', 'aksrt')];
|
||
}
|
||
} else {
|
||
return ['success' => false, 'error' => __('短信服务不可用', 'aksrt')];
|
||
}
|
||
|
||
$user = self::get_user_by_phone($phone);
|
||
if (!$user) {
|
||
return ['success' => false, 'error' => __('该手机号未注册', 'aksrt')];
|
||
}
|
||
|
||
wp_set_current_user($user->ID);
|
||
wp_set_auth_cookie($user->ID, true);
|
||
|
||
$redirect = function_exists('aksrt_get_user_center_url')
|
||
? aksrt_get_user_center_url()
|
||
: home_url('/');
|
||
|
||
return [
|
||
'success' => true,
|
||
'user_id' => $user->ID,
|
||
'redirect' => $redirect,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 绑定手机号到当前用户
|
||
*
|
||
* @param array $post (phone, code)
|
||
* @return array
|
||
*/
|
||
public static function bind_phone(array $post): array {
|
||
if (!is_user_logged_in()) {
|
||
return ['success' => false, 'error' => __('请先登录', 'aksrt')];
|
||
}
|
||
|
||
$phone = sanitize_text_field((string) ($post['phone'] ?? ''));
|
||
$code = sanitize_text_field((string) ($post['code'] ?? ''));
|
||
|
||
if (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
|
||
return ['success' => false, 'error' => __('手机号格式不正确', 'aksrt')];
|
||
}
|
||
|
||
if (function_exists('aksrt_require_captcha')) {
|
||
$captcha_result = aksrt_require_captcha('bind_phone', $post);
|
||
if ($captcha_result !== true) {
|
||
return ['success' => false, 'error' => $captcha_result];
|
||
}
|
||
}
|
||
|
||
// 验证 SMS 验证码
|
||
if (class_exists('\AKSRT\Sms\SMS')) {
|
||
$verify_result = \AKSRT\Sms\SMS::get_instance()->verify_code($phone, $code);
|
||
if (empty($verify_result['success'])) {
|
||
return ['success' => false, 'error' => $verify_result['message'] ?? __('验证码不正确', 'aksrt')];
|
||
}
|
||
} else {
|
||
return ['success' => false, 'error' => __('短信服务不可用', 'aksrt')];
|
||
}
|
||
|
||
// 检查手机号是否已被其他用户绑定
|
||
$existing = self::get_user_by_phone($phone);
|
||
if ($existing && (int) $existing->ID !== get_current_user_id()) {
|
||
return ['success' => false, 'error' => __('该手机号已被其他账号绑定', 'aksrt')];
|
||
}
|
||
|
||
$user_id = get_current_user_id();
|
||
update_user_meta($user_id, 'aksrt_phone', $phone);
|
||
update_user_meta($user_id, 'aksrt_phone_verified', '1');
|
||
|
||
return [
|
||
'success' => true,
|
||
'message' => __('手机号绑定成功', 'aksrt'),
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 根据手机号查找用户
|
||
*/
|
||
private static function get_user_by_phone(string $phone): ?\WP_User {
|
||
$users = get_users([
|
||
'meta_key' => 'aksrt_phone',
|
||
'meta_value' => $phone,
|
||
'number' => 1,
|
||
'fields' => 'all',
|
||
]);
|
||
return !empty($users) ? $users[0] : null;
|
||
}
|
||
}
|