feat(sms): align provider and verification flow
This commit is contained in:
@@ -72,31 +72,33 @@ trait UserControllerAuthPhoneOperations {
|
||||
return 'aksrt_auth_code_' . md5($scene . '|' . strtolower(trim($target)));
|
||||
}
|
||||
|
||||
private function auth_store_email_code(string $scene, string $target, string $code): void {
|
||||
private function auth_store_code(string $scene, string $target, string $code, int $ttl = 10 * MINUTE_IN_SECONDS): void {
|
||||
set_transient($this->auth_code_key($scene, $target), [
|
||||
'hash' => wp_hash_password($code),
|
||||
'attempts' => 0,
|
||||
], 10 * MINUTE_IN_SECONDS);
|
||||
'expires' => time() + $ttl,
|
||||
], $ttl);
|
||||
}
|
||||
|
||||
private function auth_consume_code(string $scene, string $target, string $code, string $channel): bool {
|
||||
if ($channel === 'phone') {
|
||||
if (!class_exists('\AKSRT\Sms\SMS')) {
|
||||
return false;
|
||||
}
|
||||
$result = \AKSRT\Sms\SMS::get_instance()->verify_code($target, $code);
|
||||
return !empty($result['success']);
|
||||
}
|
||||
|
||||
private function auth_consume_code(string $scene, string $target, string $code): bool {
|
||||
$key = $this->auth_code_key($scene, $target);
|
||||
$stored = get_transient($key);
|
||||
if (!is_array($stored) || empty($stored['hash'])) {
|
||||
return false;
|
||||
}
|
||||
$remaining = (int) ($stored['expires'] ?? 0) - time();
|
||||
if ($remaining <= 0) {
|
||||
delete_transient($key);
|
||||
return false;
|
||||
}
|
||||
$attempts = (int) ($stored['attempts'] ?? 0) + 1;
|
||||
if ($attempts > 6 || !wp_check_password($code, (string) $stored['hash'])) {
|
||||
if ($attempts > 6) {
|
||||
delete_transient($key);
|
||||
return false;
|
||||
}
|
||||
if (!wp_check_password($code, (string) $stored['hash'])) {
|
||||
$stored['attempts'] = $attempts;
|
||||
set_transient($key, $stored, 10 * MINUTE_IN_SECONDS);
|
||||
set_transient($key, $stored, $remaining);
|
||||
return false;
|
||||
}
|
||||
delete_transient($key);
|
||||
@@ -104,7 +106,7 @@ trait UserControllerAuthPhoneOperations {
|
||||
}
|
||||
|
||||
private function auth_channel(string $target, string $requested = ''): string {
|
||||
if ($requested === 'phone' || preg_match('/^1[3-9]\d{9}$/', $target)) {
|
||||
if ($requested === 'phone' || (class_exists('\\AKSRT\\Sms\\SMS') && \AKSRT\Sms\SMS::is_phonenumber($target))) {
|
||||
return 'phone';
|
||||
}
|
||||
return 'email';
|
||||
@@ -162,7 +164,7 @@ trait UserControllerAuthPhoneOperations {
|
||||
$enabled = $scene === 'login' ? \AKSRT\Settings\Settings::passwordless_login_enabled() : ($scene === 'register' ? \AKSRT\Settings\Settings::signup_verification_enabled() : true);
|
||||
if (!$enabled || ($type !== 'email_phone' && $type !== $channel)) return $this->error(__('当前验证方式不可用', 'aksrt'), 403);
|
||||
}
|
||||
if (($channel === 'email' && !is_email($target)) || ($channel === 'phone' && !preg_match('/^1[3-9]\d{9}$/', $target))) {
|
||||
if (($channel === 'email' && !is_email($target)) || ($channel === 'phone' && !\AKSRT\Sms\SMS::is_phonenumber($target))) {
|
||||
return $this->error(__('请输入有效的邮箱或手机号', 'aksrt'));
|
||||
}
|
||||
if ($this->auth_rate_limited('code:' . $scene, $target, 5, HOUR_IN_SECONDS)) {
|
||||
@@ -183,10 +185,12 @@ trait UserControllerAuthPhoneOperations {
|
||||
if (!class_exists('\AKSRT\Sms\SMS')) {
|
||||
return $this->error(__('短信服务不可用', 'aksrt'), 503);
|
||||
}
|
||||
$result = \AKSRT\Sms\SMS::get_instance()->send_code($target);
|
||||
$code = (string) random_int(100000, 999999);
|
||||
$result = \AKSRT\Sms\SMS::get_instance()->send_code($target, $code);
|
||||
if (empty($result['success'])) {
|
||||
return $this->error((string) ($result['message'] ?? __('验证码发送失败', 'aksrt')));
|
||||
}
|
||||
$this->auth_store_code($scene, $target, $code, 30 * MINUTE_IN_SECONDS);
|
||||
return $this->success(['message' => $generic, 'countdown' => (int) ($result['remaining_time'] ?? 60)]);
|
||||
}
|
||||
|
||||
@@ -215,7 +219,7 @@ trait UserControllerAuthPhoneOperations {
|
||||
}
|
||||
return $this->error(__('验证码发送失败,请稍后再试', 'aksrt'), 500);
|
||||
}
|
||||
$this->auth_store_email_code($scene, $target, $code);
|
||||
$this->auth_store_code($scene, $target, $code);
|
||||
return $this->success(['message' => $generic, 'countdown' => 60]);
|
||||
|
||||
}
|
||||
@@ -239,7 +243,7 @@ trait UserControllerAuthPhoneOperations {
|
||||
) {
|
||||
return $this->error(__('当前登录方式已关闭', 'aksrt'), 403);
|
||||
}
|
||||
if ($target === '' || (preg_match('/^1[3-9]\d{9}$/', $target) && empty($payload['code']) && class_exists('\AKSRT\Settings\Settings') && !\AKSRT\Settings\Settings::signin_phone_enabled())) {
|
||||
if ($target === '' || (\AKSRT\Sms\SMS::is_phonenumber($target) && empty($payload['code']) && class_exists('\AKSRT\Settings\Settings') && !\AKSRT\Settings\Settings::signin_phone_enabled())) {
|
||||
return $this->error(__('账号或密码错误', 'aksrt'), 401);
|
||||
}
|
||||
if ($this->auth_rate_limited('login', $target)) {
|
||||
@@ -261,7 +265,7 @@ trait UserControllerAuthPhoneOperations {
|
||||
return $this->error(__('当前验证方式不可用', 'aksrt'), 403);
|
||||
}
|
||||
$user = $this->auth_find_user($target, $channel);
|
||||
if (!$user || !$this->auth_consume_code('login', $target, sanitize_text_field((string) $payload['code']), $channel)) {
|
||||
if (!$user || !$this->auth_consume_code('login', $target, sanitize_text_field((string) $payload['code']))) {
|
||||
return $this->error(__('账号或验证码错误', 'aksrt'), 401);
|
||||
}
|
||||
if ($oauth_data) {
|
||||
@@ -348,7 +352,7 @@ trait UserControllerAuthPhoneOperations {
|
||||
if (!$this->auth_channel_allowed($configured, $channel)) {
|
||||
return $this->error(__('当前注册方式不可用', 'aksrt'), 403);
|
||||
}
|
||||
if (($channel === 'email' && !is_email($target)) || ($channel === 'phone' && !preg_match('/^1[3-9]\d{9}$/', $target))) {
|
||||
if (($channel === 'email' && !is_email($target)) || ($channel === 'phone' && !\AKSRT\Sms\SMS::is_phonenumber($target))) {
|
||||
return $this->error(__('请输入有效的邮箱或手机号', 'aksrt'));
|
||||
}
|
||||
$captcha_error = $this->auth_require_captcha('register', $payload);
|
||||
@@ -357,7 +361,7 @@ trait UserControllerAuthPhoneOperations {
|
||||
}
|
||||
$verification_completed = false;
|
||||
if ($signup_verification) {
|
||||
if (!$this->auth_consume_code('register', $target, sanitize_text_field((string) ($payload['code'] ?? '')), $channel)) {
|
||||
if (!$this->auth_consume_code('register', $target, sanitize_text_field((string) ($payload['code'] ?? '')))) {
|
||||
return $this->error(__('验证码错误或已过期', 'aksrt'));
|
||||
}
|
||||
$verification_completed = true;
|
||||
@@ -426,7 +430,7 @@ trait UserControllerAuthPhoneOperations {
|
||||
return $this->error(__('当前验证方式不可用', 'aksrt'), 403);
|
||||
}
|
||||
}
|
||||
if (($channel === 'email' && !is_email($target)) || ($channel === 'phone' && !preg_match('/^1[3-9]\d{9}$/', $target))) {
|
||||
if (($channel === 'email' && !is_email($target)) || ($channel === 'phone' && !\AKSRT\Sms\SMS::is_phonenumber($target))) {
|
||||
return $this->error(__('请输入有效的邮箱或手机号', 'aksrt'));
|
||||
}
|
||||
if ($this->auth_rate_limited('reset-password', $target, 8, HOUR_IN_SECONDS)) {
|
||||
@@ -446,7 +450,7 @@ trait UserControllerAuthPhoneOperations {
|
||||
if ($password_check !== true) {
|
||||
return $this->error((string) $password_check);
|
||||
}
|
||||
if (!$user || !$this->auth_consume_code('resetpassword', $target, sanitize_text_field((string) ($payload['code'] ?? '')), $channel)) {
|
||||
if (!$user || !$this->auth_consume_code('resetpassword', $target, sanitize_text_field((string) ($payload['code'] ?? '')))) {
|
||||
return $this->error(__('账号或验证码错误', 'aksrt'));
|
||||
}
|
||||
wp_set_password($password, (int) $user->ID);
|
||||
@@ -459,16 +463,19 @@ trait UserControllerAuthPhoneOperations {
|
||||
return $this->error(__('安全验证失败', 'aksrt'), 403);
|
||||
}
|
||||
$phone = sanitize_text_field((string) ($payload['phone'] ?? ''));
|
||||
if (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
|
||||
if (!\AKSRT\Sms\SMS::is_phonenumber($phone)) {
|
||||
return $this->error(__('手机号格式不正确', 'aksrt'));
|
||||
}
|
||||
$captcha_error = $this->auth_require_captcha('bind_phone', $payload);
|
||||
if ($captcha_error) return $captcha_error;
|
||||
if (!class_exists('\AKSRT\Sms\SMS')) return $this->error(__('短信服务不可用', 'aksrt'), 503);
|
||||
$result = \AKSRT\Sms\SMS::get_instance()->send_code($phone);
|
||||
return !empty($result['success'])
|
||||
? $this->success(['message' => __('验证码已发送', 'aksrt'), 'countdown' => (int) ($result['remaining_time'] ?? 60)])
|
||||
: $this->error((string) ($result['message'] ?? __('发送失败', 'aksrt')));
|
||||
$code = (string) random_int(100000, 999999);
|
||||
$result = \AKSRT\Sms\SMS::get_instance()->send_code($phone, $code);
|
||||
if (empty($result['success'])) {
|
||||
return $this->error((string) ($result['message'] ?? __('发送失败', 'aksrt')));
|
||||
}
|
||||
$this->auth_store_code('bind_phone', $phone, $code, 30 * MINUTE_IN_SECONDS);
|
||||
return $this->success(['message' => __('验证码短信已发送', 'aksrt'), 'countdown' => (int) ($result['remaining_time'] ?? 60)]);
|
||||
}
|
||||
|
||||
public function user_bind_phone(\WP_REST_Request $request): \WP_REST_Response {
|
||||
@@ -476,8 +483,25 @@ trait UserControllerAuthPhoneOperations {
|
||||
if (!$this->auth_verify_rest_nonce($request, $payload)) return $this->error(__('安全验证失败', 'aksrt'), 403);
|
||||
$captcha_error = $this->auth_require_captcha('bind_phone', $payload);
|
||||
if ($captcha_error) return $captcha_error;
|
||||
$phone = sanitize_text_field((string) ($payload['phone'] ?? ''));
|
||||
$code = sanitize_text_field((string) ($payload['code'] ?? ''));
|
||||
if (!$this->auth_consume_code('bind_phone', $phone, $code)) {
|
||||
return $this->error(__('验证码错误或已过期', 'aksrt'));
|
||||
}
|
||||
if (!class_exists('\AKSRT\Auth\Service')) return $this->error(__('认证服务不可用', 'aksrt'), 500);
|
||||
$result = \AKSRT\Auth\Service::bind_phone($payload);
|
||||
$result = \AKSRT\Auth\Service::bind_phone($payload, true);
|
||||
return !empty($result['success']) ? $this->success(['message' => $result['message'] ?? __('绑定成功', 'aksrt')]) : $this->error((string) ($result['error'] ?? __('绑定失败', 'aksrt')));
|
||||
}
|
||||
|
||||
public function test_sms_send(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$phone = sanitize_text_field((string) ($params['phone'] ?? ''));
|
||||
if (!class_exists('\\AKSRT\\Sms\\SMS')) {
|
||||
return $this->error(__('短信服务不可用', 'aksrt'), 503);
|
||||
}
|
||||
$result = \AKSRT\Sms\SMS::get_instance()->test_send($phone);
|
||||
return !empty($result['success'])
|
||||
? $this->success(['message' => __('测试短信已发送', 'aksrt')])
|
||||
: $this->error((string) ($result['message'] ?? __('测试短信发送失败', 'aksrt')));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user