feat(security): 新增网站安全扩展模块并支持修改手机脱敏验证

This commit is contained in:
kate522 committed 2026-10-06 09:26:55 +08:00
1 parent 224f86ec08
commit af72364078
30 files changed
+1389 -73

No files matched your search

@@ -128,8 +128,12 @@ namespace {
return 'aksrt_account_code_7_' . $scene . '_' . md5(strtolower(trim($target)));
}
function account_settings_rate_key(string $scene, string $target): string {
return 'aksrt_account_email_rate_7_' . $scene . '_' . md5(strtolower(trim($target)));
function account_settings_send_cooldown_key(string $target): string {
return 'aksrt_send_cd_' . md5(strtolower(trim($target)));
}
function account_settings_error_frequency_key(): string {
return 'aksrt_err_freq_test';
}
function account_settings_code(string $scene, string $target): string {
@@ -165,6 +169,39 @@ namespace AKSRT\Auth {
}
}
namespace AKSRT\Extensions {
final class SendLimit {
public static function check(string $target): array {
$sent_at = (int) get_transient('aksrt_send_cd_' . md5(strtolower(trim($target))));
if ($sent_at > 0 && time() - $sent_at < 60) {
return ['ok' => false, 'message' => 'cooldown', 'remaining' => 60 - (time() - $sent_at)];
}
return ['ok' => true, 'message' => '', 'remaining' => 0];
}
public static function record(string $target): int {
set_transient('aksrt_send_cd_' . md5(strtolower(trim($target))), time(), 60);
return 60;
}
}
final class SecurityHardening {
public static function error_frequency_wait(): int {
$state = get_transient('aksrt_err_freq_test');
$count = is_array($state) ? (int) ($state['count'] ?? 0) : 0;
if ($count < 5) return 0;
return max(0, ($count >= 10 ? 180 : 30) - (time() - (int) ($state['last'] ?? 0)));
}
public static function error_frequency_record(): void {
$state = get_transient('aksrt_err_freq_test');
$count = is_array($state) ? (int) ($state['count'] ?? 0) + 1 : 1;
set_transient('aksrt_err_freq_test', ['count' => $count, 'last' => time()], 600);
}
public static function error_frequency_clear(): void {
delete_transient('aksrt_err_freq_test');
}
}
}
namespace {
$GLOBALS['aksrt_account_user'] = new WP_User(7, 'old@example.test', 'Test User', 'hash:OldPass123');
$send_action = 'wp_ajax_aksrt_send_account_email_code';
@@ -181,6 +218,9 @@ namespace {
$old_send = account_settings_request($send_action, ['scene' => 'verify_email']);
account_settings_check(!empty($old_send['success']), 'old email verification code could not be sent');
account_settings_check($old_send['data']['countdown'] === 60, 'old email code send did not surface the cooldown countdown');
$cooldown_send = account_settings_request($send_action, ['scene' => 'verify_email']);
account_settings_check(empty($cooldown_send['success']), 'email code resend was not blocked by the send cooldown');
$old_code = account_settings_code('verify_email', 'old@example.test');
$bad_old_code = account_settings_request($verify_action, ['code' => 'wrong']);
account_settings_check(empty($bad_old_code['success']), 'email verification accepted an incorrect old-email code');
@@ -191,7 +231,7 @@ namespace {
account_settings_check(empty($expired_old_code['success']), 'email verification accepted an expired old-email code');
account_settings_check(get_transient(account_settings_code_key('verify_email', 'old@example.test')) === false, 'expired old-email code was not discarded');
account_settings_check(!aksrt_account_consume_proof(7, 'email_old', 'old@example.test', false), 'expired old-email code issued an old-email proof');
delete_transient(account_settings_rate_key('verify_email', 'old@example.test'));
delete_transient(account_settings_send_cooldown_key('old@example.test'));
$old_send = account_settings_request($send_action, ['scene' => 'verify_email']);
account_settings_check(!empty($old_send['success']), 'old email code could not be resent after expiry');
$old_code = account_settings_code('verify_email', 'old@example.test');
@@ -220,7 +260,7 @@ namespace {
account_settings_check(empty($expired_new_code['success']), 'email update accepted an expired new-email code');
account_settings_check(aksrt_account_consume_proof(7, 'email_old', 'old@example.test', false), 'expired new-email code validation consumed the old-email proof');
account_settings_check(get_transient(account_settings_code_key('change_email', $new_email)) === false, 'expired new-email code was not discarded');
delete_transient(account_settings_rate_key('change_email', $new_email));
delete_transient(account_settings_send_cooldown_key($new_email));
$new_send = account_settings_request($send_action, ['scene' => 'change_email', 'new_email' => $new_email]);
account_settings_check(!empty($new_send['success']), 'new email code could not be resent after expiry');
$new_code = account_settings_code('change_email', $new_email);
@@ -266,11 +306,11 @@ namespace {
}
$limited_password = account_settings_request($change_password_action, ['password' => 'NewPass123', 'password2' => 'NewPass123', 'passwordold' => 'OldPass123']);
account_settings_check(empty($limited_password['success']), 'password change did not apply its old-password attempt limit');
delete_transient('aksrt_account_rate_change_password_7');
delete_transient(account_settings_error_frequency_key());
$changed_password = account_settings_request($change_password_action, ['password' => 'NewPass123', 'password2' => 'NewPass123', 'passwordold' => 'OldPass123']);
account_settings_check(!empty($changed_password['success']), 'password change rejected the correct old password');
account_settings_check($GLOBALS['aksrt_account_user']->user_pass === 'hash:NewPass123', 'password change did not persist the new password');
account_settings_check(get_transient('aksrt_account_rate_change_password_7') === false, 'successful password change did not reset its error limit');
account_settings_check(get_transient(account_settings_error_frequency_key()) === false, 'successful password change did not reset its error limit');
update_user_meta(7, 'oauth_new', 1);
$oauth_password = account_settings_request($change_password_action, ['password' => 'OAuthPass123', 'password2' => 'OAuthPass123']);