feat: 添加身份认证服务商配置页

This commit is contained in:
2026-08-01 12:06:55 +08:00
parent cc33664c1b
commit 92e445dbd4
7 changed files with 538 additions and 1 deletions
+20
View File
@@ -11,6 +11,7 @@ class UserController extends ControllerBase {
use UserControllerReportOperations;
use UserControllerInviteOperations;
use UserControllerVerifyIntegrations;
use UserControllerIdentitySettings;
use UserControllerAuthPhoneOperations;
use UserControllerRestrictionOperations;
use UserControllerFollowOperations;
@@ -260,6 +261,25 @@ class UserController extends ControllerBase {
'permission_callback' => function () { return is_user_logged_in(); },
]);
// Identity verification provider settings (admin only)
register_rest_route(self::API_NAMESPACE, '/identity/settings', [
[
'methods' => 'GET',
'callback' => [$this, 'get_identity_settings'],
'permission_callback' => [$this, 'check_permission'],
],
[
'methods' => 'POST',
'callback' => [$this, 'update_identity_settings'],
'permission_callback' => [$this, 'check_permission'],
],
]);
register_rest_route(self::API_NAMESPACE, '/identity/settings/test', [
'methods' => 'POST',
'callback' => [$this, 'test_identity_settings'],
'permission_callback' => [$this, 'check_permission'],
]);
// SMS provider status
register_rest_route(self::API_NAMESPACE, '/sms/status', [
'methods' => 'GET',
@@ -0,0 +1,49 @@
<?php
/**
* Admin REST handlers for identity provider configuration.
*
* @package AKSRT
*/
namespace AKSRT\Admin;
use AKSRT\Settings\IdentityAdmin;
defined('ABSPATH') || exit;
trait UserControllerIdentitySettings {
public function get_identity_settings(): \WP_REST_Response {
return $this->success([
'data' => $this->identity_settings_response(IdentityAdmin::read()),
]);
}
public function update_identity_settings(\WP_REST_Request $request): \WP_REST_Response {
$settings = IdentityAdmin::update($this->get_json_params($request));
return $this->success([
'message' => __('身份认证服务商配置已保存', 'aksrt'),
'data' => $this->identity_settings_response($settings),
]);
}
public function test_identity_settings(\WP_REST_Request $request): \WP_REST_Response {
$params = $this->get_json_params($request);
$provider = sanitize_key((string) ($params['provider'] ?? ''));
$result = IdentityAdmin::test($provider);
if (empty($result['success'])) {
return $this->error((string) ($result['message'] ?? __('配置检查失败', 'aksrt')), 422);
}
return $this->success([
'message' => (string) $result['message'],
'data' => ['provider' => $provider, 'configured' => true],
]);
}
private function identity_settings_response(array $settings): array {
$settings['callback_url'] = rest_url(self::API_NAMESPACE . '/identity/callback');
return $settings;
}
}
+158
View File
@@ -0,0 +1,158 @@
<?php
/**
* Identity provider configuration storage for the admin REST surface.
*
* @package AKSRT
*/
namespace AKSRT\Settings;
defined('ABSPATH') || exit;
final class IdentityAdmin {
private const OPTION_KEY = 'aksrt_identity_settings';
private const PROVIDER_FIELDS = [
'aliyun' => ['access_key_id', 'access_key_secret'],
'tencent' => ['secret_id', 'secret_key'],
];
public static function read(): array {
$stored = get_option(self::OPTION_KEY, []);
$stored = is_array($stored) ? $stored : [];
$provider = sanitize_key((string) ($stored['provider'] ?? 'aliyun'));
if (!isset(self::PROVIDER_FIELDS[$provider])) {
$provider = 'aliyun';
}
return [
'provider' => $provider,
'providers' => [
'aliyun' => self::provider_state('aliyun', $stored),
'tencent' => self::provider_state('tencent', $stored),
],
];
}
public static function update(array $input): array {
$stored = get_option(self::OPTION_KEY, []);
$stored = is_array($stored) ? $stored : [];
if (array_key_exists('provider', $input)) {
$provider = sanitize_key((string) $input['provider']);
if (isset(self::PROVIDER_FIELDS[$provider])) {
$stored['provider'] = $provider;
}
}
$provider_input = isset($input['providers']) && is_array($input['providers'])
? $input['providers']
: [];
foreach (self::PROVIDER_FIELDS as $provider => $fields) {
$changes = isset($provider_input[$provider]) && is_array($provider_input[$provider])
? $provider_input[$provider]
: [];
if (!empty($changes['clear_credentials'])) {
unset($stored['providers'][$provider]);
continue;
}
$credentials = isset($changes['credentials']) && is_array($changes['credentials'])
? $changes['credentials']
: [];
foreach ($fields as $field) {
if (!isset($credentials[$field]) || trim((string) $credentials[$field]) === '') {
continue;
}
$stored['providers'][$provider][$field] = self::encrypt(
sanitize_text_field((string) $credentials[$field])
);
}
}
update_option(self::OPTION_KEY, $stored, false);
return self::read();
}
public static function test(string $provider): array {
$settings = self::read();
if (!isset(self::PROVIDER_FIELDS[$provider])) {
return ['success' => false, 'message' => '不支持的身份认证服务商'];
}
$state = $settings['providers'][$provider];
if (!$state['configured']) {
return [
'success' => false,
'message' => sprintf('%s凭证不完整,请补充后保存', $state['label']),
];
}
return [
'success' => true,
'message' => sprintf('%s配置检查通过,凭证字段完整', $state['label']),
];
}
private static function encrypt(string $value): string {
if ($value === '' || !function_exists('openssl_encrypt')) {
return '';
}
$key = hash('sha256', wp_salt('auth'), true);
$iv = random_bytes(12);
$tag = '';
$ciphertext = openssl_encrypt($value, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag);
if ($ciphertext === false) {
return '';
}
return base64_encode($iv . $tag . $ciphertext);
}
private static function decrypt(string $encoded): string {
if ($encoded === '' || !function_exists('openssl_decrypt')) {
return '';
}
$payload = base64_decode($encoded, true);
if ($payload === false || strlen($payload) <= 28) {
return '';
}
$iv = substr($payload, 0, 12);
$tag = substr($payload, 12, 16);
$ciphertext = substr($payload, 28);
$key = hash('sha256', wp_salt('auth'), true);
$plaintext = openssl_decrypt($ciphertext, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag);
return is_string($plaintext) ? $plaintext : '';
}
private static function provider_state(string $provider, array $stored): array {
$credentials = isset($stored['providers'][$provider]) && is_array($stored['providers'][$provider])
? $stored['providers'][$provider]
: [];
$missing = [];
$configured_fields = [];
foreach (self::PROVIDER_FIELDS[$provider] as $field) {
$has_value = self::decrypt((string) ($credentials[$field] ?? '')) !== '';
$configured_fields[$field] = $has_value;
if (!$has_value) {
$missing[] = $field;
}
}
return [
'label' => $provider === 'aliyun' ? '阿里云' : '腾讯云',
'configured' => $missing === [],
'configured_fields' => $configured_fields,
'missing_fields' => $missing,
];
}
}