Files
wordpress-theme-aksrt/inc/classes/auth-traits/trait-aksrt-auth-service-part-1a.php
T
kate522 02f77b756c fix(captcha): close human-verification loop end-to-end
Rewrite captcha core around aksrt_captcha_config only, require captcha
on login/comment/SMS/phone auth and related gates, wire frontend token
collection, and remove dead dual-source security settings plus slider enqueue.
2026-07-13 19:52:30 +08:00

476 lines
17 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
defined('ABSPATH') || exit;
trait AKSRT_Auth_Service_Part_1a {
public static function register_user(array $post, array $context = []): array {
// 先 reset pending invite context,避免同一进程内残留污染
self::$pending_invite_inviter_id = 0;
self::$pending_invite_code = '';
$user_login = sanitize_user(wp_unslash($post['user_login'] ?? ''), true);
$user_email = sanitize_email(wp_unslash($post['user_email'] ?? ''));
$password = (string) ($post['user_pass'] ?? '');
$confirm = (string) ($post['user_pass_confirm'] ?? '');
if (!wp_verify_nonce($post['_register_nonce'] ?? '', 'aksrt_register')) {
return ['success' => false, 'error' => __('安全验证失败,请刷新页面重试', 'aksrt')];
}
if (empty($post['agree_terms'])) {
return ['success' => false, 'error' => __('请先同意服务条款', 'aksrt')];
}
if (empty($user_login) || !validate_username($user_login)) {
return ['success' => false, 'error' => __('请输入有效的用户名', 'aksrt')];
}
if (mb_strlen($user_login) < 3 || mb_strlen($user_login) > 60) {
return ['success' => false, 'error' => __('用户名长度需在 3-60 个字符之间', 'aksrt')];
}
if (username_exists($user_login)) {
return ['success' => false, 'error' => __('该用户名已被使用', 'aksrt')];
}
if (empty($user_email) || !is_email($user_email)) {
return ['success' => false, 'error' => __('请输入有效的邮箱地址', 'aksrt')];
}
if (email_exists($user_email)) {
return ['success' => false, 'error' => __('该邮箱已被其他账号使用', 'aksrt')];
}
$password_check = self::validate_password($password, $confirm);
if ($password_check !== true) {
return ['success' => false, 'error' => $password_check];
}
if (function_exists('aksrt_require_captcha')) {
$captcha_result = aksrt_require_captcha('register', $post);
if ($captcha_result !== true) {
return ['success' => false, 'error' => $captcha_result];
}
}
// ====== 解析邀请码 ======
// 优先级:表单 invite_code(用户显式填写) > cookie aksrt_invite
$invite_registration_enabled = class_exists('AKSRT_Settings') && AKSRT_Settings::invite_registration_enabled();
$invite_mode = $invite_registration_enabled && class_exists('AKSRT_Settings')
? AKSRT_Settings::invite_register_field_mode()
: 'hidden';
$invite_code = isset($post['invite_code']) ? sanitize_key((string) wp_unslash($post['invite_code'])) : '';
$resolved_inviter_id = 0;
$invite_is_valid = false;
if (!$invite_registration_enabled) {
// 邀请功能关闭,忽略所有邀请码
$invite_code = '';
} elseif ($invite_code !== '') {
if (class_exists('AKSRT_Invite')) {
$invite_service = AKSRT_Invite::get_instance();
$resolved_inviter_id = $invite_service->resolve_code($invite_code);
$invite_is_valid = $invite_service->validate_code($invite_code);
}
// 必填模式下,邀请码无效时根据后台开关决定是否拒绝
$reject_invalid = class_exists('AKSRT_Settings') && AKSRT_Settings::invite_reject_invalid();
if ($invite_mode === 'required' && $reject_invalid && !$invite_is_valid) {
return ['success' => false, 'error' => __('邀请码无效,请检查后重试', 'aksrt')];
}
} elseif ($invite_mode === 'required') {
return ['success' => false, 'error' => __('请填写邀请码', 'aksrt')];
}
// 把解析到的 inviter_id 暂存到静态字段,user_register hook 内部读取(优先于 cookie)
self::$pending_invite_inviter_id = $resolved_inviter_id;
self::$pending_invite_code = $invite_code;
$user_id = wp_create_user($user_login, $password, $user_email);
if (is_wp_error($user_id)) {
self::$pending_invite_inviter_id = 0;
self::$pending_invite_code = '';
return ['success' => false, 'error' => $user_id->get_error_message()];
}
if ($invite_code !== '' && $resolved_inviter_id <= 0 && class_exists('AKSRT_Invite')) {
AKSRT_Invite::get_instance()->handle_registration_invite((int) $user_id, $invite_code);
}
// wp_create_user 内部已触发 user_register hook,AKSRT_Invite::on_user_register 已读取 pending 字段。
// 这里清空,避免污染后续请求。
self::$pending_invite_inviter_id = 0;
self::$pending_invite_code = '';
$user = get_userdata($user_id);
if (!$user || is_wp_error($user)) {
return ['success' => false, 'error' => __('注册成功,但读取用户信息失败,请联系管理员', 'aksrt')];
}
$oauth_context = isset($context['oauth_bind_data']) && is_array($context['oauth_bind_data']) ? $context['oauth_bind_data'] : [];
$oauth_verified = self::oauth_email_is_verified($oauth_context);
$requires_verify = AKSRT_Settings::registration_requires_email_verification() && !$oauth_verified;
if (!$requires_verify) {
self::mark_email_verified($user_id);
return [
'success' => true,
'user_id' => (int) $user_id,
'user' => $user,
'user_login' => $user_login,
];
}
$method = AKSRT_Settings::registration_verification_method();
$begin = self::begin_email_verification(
$user,
$method,
[
'obk' => sanitize_text_field((string) ($context['obk'] ?? '')),
]
);
if (empty($begin['success'])) {
self::delete_user_account($user_id);
return $begin;
}
return [
'success' => true,
'user_id' => (int) $user_id,
'user' => $user,
'user_login' => $user_login,
'verification_required' => true,
'verification_method' => $method,
'verify_url' => self::get_email_verification_url((int) $user_id, ['mode' => $method]),
'message' => $begin['message'] ?? '',
];
}
public static function bind_oauth_account(array $post, array $oauth_data): array {
if (!wp_verify_nonce($post['_bind_nonce'] ?? '', 'aksrt_bind')) {
return ['success' => false, 'error' => __('安全验证失败,请刷新页面重试', 'aksrt')];
}
if (function_exists('aksrt_require_captcha')) {
$captcha_result = aksrt_require_captcha('bind', $post);
if ($captcha_result !== true) {
return ['success' => false, 'error' => $captcha_result];
}
}
$creds = [
'user_login' => self::resolve_email_to_username((string) ($post['log'] ?? '')),
'user_password' => (string) ($post['pwd'] ?? ''),
'remember' => true,
];
$user = wp_signon($creds);
if (is_wp_error($user)) {
return ['success' => false, 'error' => self::friendly_login_error($user->get_error_message())];
}
if (self::user_requires_email_verification($user)) {
wp_logout();
return [
'success' => false,
'error' => __('该账号尚未完成邮箱验证,请先完成验证后再绑定第三方登录。', 'aksrt'),
'verify_url' => self::get_email_verification_url((int) $user->ID),
];
}
$provider = sanitize_key((string) ($oauth_data['provider'] ?? ''));
if ($provider === '') {
return ['success' => false, 'error' => __('绑定数据无效,请重新使用第三方登录', 'aksrt')];
}
$uid = (int) $user->ID;
update_user_meta($uid, "aksrt_{$provider}_id", $oauth_data['provider_id'] ?? '');
$bindings = get_user_meta($uid, 'aksrt_oauth_bindings', true) ?: [];
if (!is_array($bindings)) {
$bindings = [];
}
if (!in_array($provider, $bindings, true)) {
$bindings[] = $provider;
}
update_user_meta($uid, 'aksrt_oauth_bindings', $bindings);
if (!empty($oauth_data['user_data']['avatar_url'])) {
update_user_meta($uid, 'aksrt_oauth_avatar', esc_url_raw($oauth_data['user_data']['avatar_url']));
}
if (!empty($post['obk'])) {
delete_transient(sanitize_text_field((string) $post['obk']));
}
wp_set_auth_cookie($uid, true);
return [
'success' => true,
'user' => $user,
'provider' => $provider,
];
}
public static function finalize_oauth_registration(int $user_id, array $oauth_data, string $obk = ''): void {
if ($user_id <= 0) {
return;
}
$provider = sanitize_key((string) ($oauth_data['provider'] ?? ''));
if ($provider === '') {
return;
}
update_user_meta($user_id, "aksrt_{$provider}_id", $oauth_data['provider_id'] ?? '');
update_user_meta($user_id, 'aksrt_oauth_bindings', [$provider]);
if (!empty($oauth_data['user_data']['avatar_url'])) {
update_user_meta($user_id, 'aksrt_oauth_avatar', esc_url_raw($oauth_data['user_data']['avatar_url']));
}
if (self::oauth_email_is_verified($oauth_data)) {
AKSRT_Auth_Email_Verification::mark_email_verified($user_id);
}
if ($obk !== '') {
delete_transient(sanitize_text_field($obk));
}
wp_set_auth_cookie($user_id, true);
}
public static function get_email_verification_context(array $query): array {
$invalid_payload = [
'mode' => 'invalid',
'user' => null,
'error' => __('验证入口无效或已过期,请重新发起验证流程。', 'aksrt'),
'method' => '',
'email' => '',
'uid' => 0,
'verified' => false,
'access_key' => '',
];
$user_id = isset($query['uid']) ? absint($query['uid']) : 0;
if ($user_id <= 0) {
return $invalid_payload;
}
$user = get_userdata($user_id);
if (!$user || is_wp_error($user)) {
return $invalid_payload;
}
$method = self::current_registration_verification_method();
$email = (string) get_user_meta($user_id, self::EMAIL_TARGET_META, true);
$token = sanitize_text_field((string) ($query['token'] ?? ''));
$access = sanitize_text_field((string) ($query['vk'] ?? ''));
// 容错:meta 缺失时(例如历史账号被强制要求验证),回退到设置默认值与 user_email
if (!in_array($method, ['link', 'code'], true)) {
$method = self::current_registration_verification_method();
}
if ($email === '' || !is_email($email)) {
$email = (string) $user->user_email;
}
$masked_email = self::masked_email($email);
$has_access = self::has_valid_email_access_key($user_id, $access);
// token 路径:链接模式直接走 token 校验;不依赖 vk
if ($token !== '') {
if (AKSRT_Auth_Email_Verification::is_email_verified($user_id)) {
return [
'mode' => 'invalid',
'user' => $user,
'error' => __('该验证链接已失效或已使用,请重新发起验证流程', 'aksrt'),
'method' => $has_access ? $method : '',
'email' => $has_access ? $masked_email : '',
'uid' => $has_access ? $user_id : 0,
'verified' => true,
'access_key' => $has_access ? $access : '',
];
}
$result = self::verify_email_token($user_id, $token);
if (!empty($result['success'])) {
return [
'mode' => 'completed',
'user' => $user,
'error' => '',
'method' => $method,
'email' => $masked_email,
'uid' => $user_id,
'verified' => true,
'redirect' => $result['redirect'] ?? home_url('/'),
'message' => $result['message'] ?? '',
'access_key' => $has_access ? $access : '',
];
}
return [
'mode' => 'invalid',
'user' => $user,
'error' => $result['error'] ?? __('验证链接无效或已过期', 'aksrt'),
'method' => $has_access ? $method : '',
'email' => $has_access ? $masked_email : '',
'uid' => $has_access ? $user_id : 0,
'verified' => false,
'access_key' => $has_access ? $access : '',
];
}
// 没有 token 也没有 vk:返回完全脱敏的通用错误,避免根据 uid 枚举账号状态
if (!$has_access) {
return $invalid_payload;
}
if (!self::user_requires_email_verification($user)) {
return [
'mode' => 'verified',
'user' => $user,
'error' => '',
'method' => '',
'email' => self::masked_email((string) $user->user_email),
'uid' => $user_id,
'verified' => true,
'access_key' => $access,
];
}
return [
'mode' => $method === 'code' ? 'code' : 'pending',
'user' => $user,
'error' => '',
'method' => $method,
'email' => $masked_email ?: self::masked_email((string) $user->user_email),
'uid' => $user_id,
'verified' => false,
'access_key' => $access,
];
}
public static function verify_email_code_submission(array $post): array {
if (!wp_verify_nonce($post['_verify_email_nonce'] ?? '', 'aksrt_verify_email')) {
return ['success' => false, 'error' => __('安全验证失败,请刷新页面重试', 'aksrt')];
}
$user_id = isset($post['verify_uid']) ? absint($post['verify_uid']) : 0;
$access = sanitize_text_field((string) ($post['vk'] ?? ''));
$code = sanitize_text_field((string) ($post['email_code'] ?? ''));
if ($user_id <= 0 || $code === '') {
return ['success' => false, 'error' => __('请输入完整的邮箱验证码', 'aksrt')];
}
if (!self::has_valid_email_access_key($user_id, $access)) {
return ['success' => false, 'error' => __('验证入口无效或已过期,请重新发起验证流程。', 'aksrt')];
}
if (function_exists('aksrt_require_captcha')) {
$captcha_result = aksrt_require_captcha('verify_email', $post);
if ($captcha_result !== true) {
return ['success' => false, 'error' => $captcha_result];
}
}
return self::verify_email_code($user_id, $code);
}
public static function resend_email_verification(array $post): array {
if (!wp_verify_nonce($post['_verify_email_nonce'] ?? '', 'aksrt_verify_email')) {
return ['success' => false, 'error' => __('安全验证失败,请刷新页面重试', 'aksrt')];
}
$user_id = isset($post['verify_uid']) ? absint($post['verify_uid']) : 0;
$access = sanitize_text_field((string) ($post['vk'] ?? ''));
// 必须先持有有效 vk 才允许重发,避免未登录用户用 uid 枚举触发邮件
if ($user_id <= 0 || !self::has_valid_email_access_key($user_id, $access)) {
return ['success' => false, 'error' => __('验证入口无效或已过期,请重新发起验证流程。', 'aksrt')];
}
// IP 节流:每 IP 每 RESEND_IP_WINDOW 秒最多 RESEND_IP_MAX 次(跨账号合并计数)
$ip = sanitize_text_field((string) ($_SERVER['REMOTE_ADDR'] ?? ''));
if ($ip !== '') {
$ip_key = 'aksrt_email_resend_ip_' . md5($ip);
$ip_attempts = (int) get_transient($ip_key);
if ($ip_attempts >= self::RESEND_IP_MAX) {
self::log_event('verify.resend.ip_throttled', ['uid' => $user_id, 'ip' => $ip]);
return ['success' => false, 'error' => __('操作过于频繁,请稍后再试', 'aksrt')];
}
set_transient($ip_key, $ip_attempts + 1, self::RESEND_IP_WINDOW);
}
$user = get_userdata($user_id);
if (!$user || is_wp_error($user)) {
return ['success' => false, 'error' => __('验证入口无效或已过期,请重新发起验证流程。', 'aksrt')];
}
if (!self::user_requires_email_verification($user)) {
return ['success' => false, 'error' => __('该账号已完成邮箱验证,无需重复发送', 'aksrt')];
}
if (function_exists('aksrt_require_captcha')) {
$captcha_result = aksrt_require_captcha('verify_email', $post);
if ($captcha_result !== true) {
return ['success' => false, 'error' => $captcha_result];
}
}
$method = self::current_registration_verification_method();
$email = (string) get_user_meta($user_id, self::EMAIL_TARGET_META, true);
// 容错:meta 缺失时回退到设置默认值与 user_email(兼容历史账号 / meta 被外部清理的情况)
if (!in_array($method, ['link', 'code'], true)) {
$method = AKSRT_Settings::registration_verification_method();
}
if ($email === '' || !is_email($email)) {
$email = (string) $user->user_email;
}
if ($email === '' || !is_email($email)) {
return ['success' => false, 'error' => __('当前账号缺少有效邮箱,请联系管理员', 'aksrt')];
}
$requested_at = (int) get_user_meta($user_id, self::EMAIL_REQUESTED_AT_META, true);
if ($requested_at > 0 && (time() - $requested_at) < 60) {
return ['success' => false, 'error' => __('操作过于频繁,请稍后再试', 'aksrt')];
}
return self::begin_email_verification($user, $method, [
'email' => $email,
'obk' => (string) get_user_meta($user_id, self::EMAIL_OBK_META, true),
'force' => true,
]);
}
private static function user_requires_email_verification($user): bool {
return AKSRT_Auth_Email_Verification::user_requires_email_verification($user);
}
private static function mark_email_verified(int $user_id): void {
AKSRT_Auth_Email_Verification::mark_email_verified($user_id);
}
// ============================================================
// 公开 API:管理员后台 / 内部业务调用(委托 AKSRT_Auth_Email_Verification)
// ============================================================
public static function user_is_email_verified(int $user_id): bool {
return AKSRT_Auth_Email_Verification::user_is_email_verified($user_id);
}
public static function get_user_verification_state(int $user_id): array {
return AKSRT_Auth_Email_Verification::get_user_verification_state($user_id);
}
public static function admin_mark_verified(int $user_id, int $by_admin_id = 0): array {
return AKSRT_Auth_Email_Verification::admin_mark_verified($user_id, $by_admin_id);
}
public static function admin_unmark_verified(int $user_id, int $by_admin_id = 0): array {
return AKSRT_Auth_Email_Verification::admin_unmark_verified($user_id, $by_admin_id);
}
}