diff --git a/assets/admin/src/api/modules/users.js b/assets/admin/src/api/modules/users.js
index 897e9b96..b9aed5e6 100644
--- a/assets/admin/src/api/modules/users.js
+++ b/assets/admin/src/api/modules/users.js
@@ -20,6 +20,14 @@ export const usersApiMethods = {
deleteNotification(id) { return this.request(`/notifications/${id}`, { method: 'DELETE' }) },
runNotificationCleanup() { return this.request('/notifications/cleanup', { method: 'POST', body: JSON.stringify({}) }) },
+ getIdentitySettings() { return this.request('/identity/settings') },
+ saveIdentitySettings(data) {
+ return this.request('/identity/settings', { method: 'POST', body: JSON.stringify(data) })
+ },
+ testIdentitySettings(data = {}) {
+ return this.request('/identity/settings/test', { method: 'POST', body: JSON.stringify(data) })
+ },
+
getCreditSettings() { return this.request('/credits/settings') },
saveCreditSettings(data) { return this.request('/credits/settings', { method: 'POST', body: JSON.stringify(data) }) },
listCreditUsers(params = {}) {
diff --git a/assets/admin/src/constants/sections/users.js b/assets/admin/src/constants/sections/users.js
index 5496a38e..638d4ee8 100644
--- a/assets/admin/src/constants/sections/users.js
+++ b/assets/admin/src/constants/sections/users.js
@@ -63,11 +63,12 @@ export const usersSection = {
canonicalDomain: 'users',
section: 'credits',
}),
- schemaPage({
+ customPage({
key: 'users-verify',
name: 'UsersVerify',
label: '身份认证',
route: '/users/verify',
+ component: 'UsersVerify',
title: '身份认证',
desc: '配置认证展示、申请类型与申请门槛;审核、驳回和取消认证统一在 WordPress 用户菜单处理。',
canonicalDomain: 'users',
diff --git a/assets/admin/src/pages/users/UsersVerify.vue b/assets/admin/src/pages/users/UsersVerify.vue
new file mode 100644
index 00000000..39790ea6
--- /dev/null
+++ b/assets/admin/src/pages/users/UsersVerify.vue
@@ -0,0 +1,300 @@
+
+
+
+
+
+
+
+
+
+ {{ verifyForm.user_verify_enabled ? '已启用' : '已停用' }}
+
+
+ {{ verifyForm.user_verify_enabled ? '运行中' : '未运行' }}
+
+
+
+
+
+
+ {{ providerState(key).configured ? '已配置' : '未配置' }}
+
+
+ {{ identityForm.provider === key ? '当前服务商' : '备用服务商' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 阿里云
+ 腾讯云
+
+
+
+
+
+
+ 复制
+
+ 将此地址填入服务商控制台需要的回调地址配置。
+
+
+
+
+
+
+
+
+
+ {{ providerLabel(key) }}凭证
+
+ {{ providerState(key).configured ? '已配置' : '未配置' }}
+
+
+
+
+ 已保存凭证不会回显。留空表示保留原值;输入新值并保存会替换对应凭证。
+
+
+
+
+
+
+
+ 保存时删除 {{ providerLabel(key) }}全部凭证
+
+ 清除后该服务商立即变为未配置;此操作不会因输入框留空而触发。
+
+
+
+ 测试 {{ providerLabel(key) }}配置
+
+
+
+
+
+
+
+
+ 放弃更改
+ 保存全部配置
+
+
+
+
+
+
+
+
diff --git a/assets/admin/src/router/component-loaders/users.js b/assets/admin/src/router/component-loaders/users.js
index 9f850d99..f5e91037 100644
--- a/assets/admin/src/router/component-loaders/users.js
+++ b/assets/admin/src/router/component-loaders/users.js
@@ -1,6 +1,7 @@
export const usersPageLoaders = {
UsersFeatures: () => import('@/pages/users/UsersFeatures.vue'),
UsersSignin: () => import('@/pages/users/UsersSignin.vue'),
+ UsersVerify: () => import('@/pages/users/UsersVerify.vue'),
UsersLevel: () => import('@/pages/users/UsersLevel.vue'),
UsersBadge: () => import('@/pages/users/UsersBadge.vue'),
UsersWechatTemplate: () => import('@/pages/users/UsersWechatTemplate.vue'),
diff --git a/src/AKSRT/Admin/UserController.php b/src/AKSRT/Admin/UserController.php
index 06201886..07e02238 100644
--- a/src/AKSRT/Admin/UserController.php
+++ b/src/AKSRT/Admin/UserController.php
@@ -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',
diff --git a/src/AKSRT/Admin/UserControllerIdentitySettings.php b/src/AKSRT/Admin/UserControllerIdentitySettings.php
new file mode 100644
index 00000000..540f76fa
--- /dev/null
+++ b/src/AKSRT/Admin/UserControllerIdentitySettings.php
@@ -0,0 +1,49 @@
+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;
+ }
+}
diff --git a/src/AKSRT/Settings/IdentityAdmin.php b/src/AKSRT/Settings/IdentityAdmin.php
new file mode 100644
index 00000000..134a86f6
--- /dev/null
+++ b/src/AKSRT/Settings/IdentityAdmin.php
@@ -0,0 +1,158 @@
+ ['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,
+ ];
+ }
+}