mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-11-01 00:27:30 +00:00
新增腾讯天御验证码认证门禁
This commit is contained in:
@@ -1990,7 +1990,11 @@
|
||||
{{ t("admin.settings.turnstile.enableTurnstileHint") }}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle v-model="form.turnstile_enabled" />
|
||||
<Toggle
|
||||
v-model="form.turnstile_enabled"
|
||||
data-testid="turnstile-enabled-toggle"
|
||||
@update:model-value="onTurnstileToggle"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Turnstile Keys - Only show when enabled -->
|
||||
@@ -2050,6 +2054,151 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 腾讯天御验证码设置 -->
|
||||
<div class="card">
|
||||
<div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{{ t("admin.settings.tencentCaptcha.title") }}
|
||||
</h2>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.settings.tencentCaptcha.description") }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="space-y-5 p-6">
|
||||
<div class="flex items-center justify-between gap-6">
|
||||
<div>
|
||||
<label class="font-medium text-gray-900 dark:text-white">
|
||||
{{ t("admin.settings.tencentCaptcha.enable") }}
|
||||
</label>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.settings.tencentCaptcha.enableHint") }}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle
|
||||
v-model="form.tencent_captcha_enabled"
|
||||
data-testid="tencent-captcha-enabled-toggle"
|
||||
@update:model-value="onTencentCaptchaToggle"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="form.tencent_captcha_enabled"
|
||||
class="border-t border-gray-100 pt-4 dark:border-dark-700"
|
||||
>
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<div class="md:col-span-2">
|
||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
||||
{{ t("admin.settings.tencentCaptcha.appCredentialsTitle") }}
|
||||
</h3>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.settings.tencentCaptcha.appCredentialsHint") }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ t("admin.settings.tencentCaptcha.appId") }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form.tencent_captcha_app_id"
|
||||
type="text"
|
||||
inputmode="numeric"
|
||||
class="input font-mono text-sm"
|
||||
placeholder="123456789"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ t("admin.settings.tencentCaptcha.appSecretKey") }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form.tencent_captcha_app_secret_key"
|
||||
type="password"
|
||||
autocomplete="new-password"
|
||||
class="input font-mono text-sm"
|
||||
:placeholder="t('admin.settings.tencentCaptcha.keepExisting')"
|
||||
/>
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ form.tencent_captcha_app_secret_key_configured ? t("admin.settings.tencentCaptcha.configured") : t("admin.settings.tencentCaptcha.required") }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="border-t border-gray-100 pt-5 md:col-span-2 dark:border-dark-700">
|
||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
||||
{{ t("admin.settings.tencentCaptcha.cloudCredentialsTitle") }}
|
||||
</h3>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.settings.tencentCaptcha.cloudCredentialsHint") }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ t("admin.settings.tencentCaptcha.cloudSecretId") }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form.tencent_captcha_cloud_secret_id"
|
||||
type="password"
|
||||
autocomplete="new-password"
|
||||
class="input font-mono text-sm"
|
||||
:placeholder="t('admin.settings.tencentCaptcha.keepExisting')"
|
||||
/>
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ form.tencent_captcha_cloud_secret_id_configured ? t("admin.settings.tencentCaptcha.configured") : t("admin.settings.tencentCaptcha.required") }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ t("admin.settings.tencentCaptcha.cloudSecretKey") }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form.tencent_captcha_cloud_secret_key"
|
||||
type="password"
|
||||
autocomplete="new-password"
|
||||
class="input font-mono text-sm"
|
||||
:placeholder="t('admin.settings.tencentCaptcha.keepExisting')"
|
||||
/>
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ form.tencent_captcha_cloud_secret_key_configured ? t("admin.settings.tencentCaptcha.configured") : t("admin.settings.tencentCaptcha.required") }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-5 text-xs text-amber-600 dark:text-amber-400">
|
||||
{{ t("admin.settings.tencentCaptcha.mutualExclusion") }}
|
||||
</p>
|
||||
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.settings.tencentCaptcha.camPermissionHint") }}
|
||||
</p>
|
||||
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.settings.tencentCaptcha.aidEncryptedHint") }}
|
||||
</p>
|
||||
<div class="mt-3 flex flex-wrap gap-x-4 gap-y-2 text-sm">
|
||||
<a
|
||||
href="https://console.cloud.tencent.com/captcha"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-primary-600 hover:text-primary-500"
|
||||
>
|
||||
{{ t("admin.settings.tencentCaptcha.openCaptchaConsole") }}
|
||||
</a>
|
||||
<a
|
||||
href="https://console.cloud.tencent.com/cam/capi"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-primary-600 hover:text-primary-500"
|
||||
>
|
||||
{{ t("admin.settings.tencentCaptcha.createCloudKeys") }}
|
||||
</a>
|
||||
<a
|
||||
href="https://cloud.tencent.com/document/product/1110/36841"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-primary-600 hover:text-primary-500"
|
||||
>
|
||||
{{ t("admin.settings.tencentCaptcha.openWebDocs") }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- LinuxDo Connect OAuth 登录 -->
|
||||
<div class="card">
|
||||
<div
|
||||
@@ -8779,6 +8928,9 @@ type SettingsForm = Omit<
|
||||
> & {
|
||||
smtp_password: string;
|
||||
turnstile_secret_key: string;
|
||||
tencent_captcha_app_secret_key: string;
|
||||
tencent_captcha_cloud_secret_id: string;
|
||||
tencent_captcha_cloud_secret_key: string;
|
||||
linuxdo_connect_client_secret: string;
|
||||
dingtalk_connect_client_secret: string;
|
||||
wechat_connect_app_secret: string;
|
||||
@@ -8908,6 +9060,14 @@ const form = reactive<SettingsForm>({
|
||||
turnstile_site_key: "",
|
||||
turnstile_secret_key: "",
|
||||
turnstile_secret_key_configured: false,
|
||||
tencent_captcha_enabled: false,
|
||||
tencent_captcha_app_id: "",
|
||||
tencent_captcha_app_secret_key: "",
|
||||
tencent_captcha_app_secret_key_configured: false,
|
||||
tencent_captcha_cloud_secret_id: "",
|
||||
tencent_captcha_cloud_secret_id_configured: false,
|
||||
tencent_captcha_cloud_secret_key: "",
|
||||
tencent_captcha_cloud_secret_key_configured: false,
|
||||
api_key_acl_trust_forwarded_ip: true,
|
||||
forwarded_client_ip_headers: [],
|
||||
// LinuxDo Connect OAuth 登录
|
||||
@@ -9067,6 +9227,14 @@ const form = reactive<SettingsForm>({
|
||||
allow_user_view_error_requests: false,
|
||||
});
|
||||
|
||||
function onTurnstileToggle(enabled: boolean): void {
|
||||
if (enabled) form.tencent_captcha_enabled = false;
|
||||
}
|
||||
|
||||
function onTencentCaptchaToggle(enabled: boolean): void {
|
||||
if (enabled) form.turnstile_enabled = false;
|
||||
}
|
||||
|
||||
type OpenAIAdvancedSchedulerOverrideKey =
|
||||
| "openai_advanced_scheduler_lb_top_k"
|
||||
| "openai_advanced_scheduler_weight_priority"
|
||||
@@ -10024,6 +10192,9 @@ async function loadSettings() {
|
||||
form.smtp_password = "";
|
||||
smtpPasswordManuallyEdited.value = false;
|
||||
form.turnstile_secret_key = "";
|
||||
form.tencent_captcha_app_secret_key = "";
|
||||
form.tencent_captcha_cloud_secret_id = "";
|
||||
form.tencent_captcha_cloud_secret_key = "";
|
||||
form.linuxdo_connect_client_secret = "";
|
||||
form.dingtalk_connect_client_secret = "";
|
||||
form.github_oauth_client_secret = "";
|
||||
@@ -10393,6 +10564,14 @@ async function saveSettings() {
|
||||
turnstile_enabled: form.turnstile_enabled,
|
||||
turnstile_site_key: form.turnstile_site_key,
|
||||
turnstile_secret_key: form.turnstile_secret_key || undefined,
|
||||
tencent_captcha_enabled: form.tencent_captcha_enabled,
|
||||
tencent_captcha_app_id: form.tencent_captcha_app_id,
|
||||
tencent_captcha_app_secret_key:
|
||||
form.tencent_captcha_app_secret_key || undefined,
|
||||
tencent_captcha_cloud_secret_id:
|
||||
form.tencent_captcha_cloud_secret_id || undefined,
|
||||
tencent_captcha_cloud_secret_key:
|
||||
form.tencent_captcha_cloud_secret_key || undefined,
|
||||
api_key_acl_trust_forwarded_ip: form.api_key_acl_trust_forwarded_ip,
|
||||
forwarded_client_ip_headers: form.forwarded_client_ip_headers,
|
||||
linuxdo_connect_enabled: form.linuxdo_connect_enabled,
|
||||
|
||||
@@ -396,6 +396,11 @@ const baseSettingsResponse = {
|
||||
turnstile_enabled: false,
|
||||
turnstile_site_key: "",
|
||||
turnstile_secret_key_configured: false,
|
||||
tencent_captcha_enabled: false,
|
||||
tencent_captcha_app_id: "",
|
||||
tencent_captcha_app_secret_key_configured: false,
|
||||
tencent_captcha_cloud_secret_id_configured: false,
|
||||
tencent_captcha_cloud_secret_key_configured: false,
|
||||
api_key_acl_trust_forwarded_ip: true,
|
||||
forwarded_client_ip_headers: [],
|
||||
linuxdo_connect_enabled: false,
|
||||
@@ -767,6 +772,50 @@ describe("admin SettingsView payment visible method controls", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("腾讯天御验证码与 Turnstile 开关互斥并保存四项配置", async () => {
|
||||
const wrapper = mountView();
|
||||
await flushPromises();
|
||||
await openSecurityTab(wrapper);
|
||||
|
||||
const turnstileToggle = wrapper.get('[data-testid="turnstile-enabled-toggle"]');
|
||||
const tencentToggle = wrapper.get('[data-testid="tencent-captcha-enabled-toggle"]');
|
||||
await turnstileToggle.setValue(true);
|
||||
expect((turnstileToggle.element as HTMLInputElement).checked).toBe(true);
|
||||
|
||||
await tencentToggle.setValue(true);
|
||||
expect((turnstileToggle.element as HTMLInputElement).checked).toBe(false);
|
||||
expect((tencentToggle.element as HTMLInputElement).checked).toBe(true);
|
||||
|
||||
const card = wrapper
|
||||
.findAll(".card")
|
||||
.find((node) => node.text().includes("admin.settings.tencentCaptcha.title"));
|
||||
expect(card).toBeDefined();
|
||||
expect(card!.get('a[href="https://console.cloud.tencent.com/captcha"]').exists()).toBe(true);
|
||||
expect(card!.get('a[href="https://console.cloud.tencent.com/cam/capi"]').exists()).toBe(true);
|
||||
expect(
|
||||
card!.get('a[href="https://cloud.tencent.com/document/product/1110/36841"]').exists(),
|
||||
).toBe(true);
|
||||
const inputs = card!.findAll("input").filter((input) => input.attributes("type") !== "checkbox");
|
||||
await inputs[0]!.setValue("123456789");
|
||||
await inputs[1]!.setValue("app-secret-value");
|
||||
await inputs[2]!.setValue("cloud-secret-id-value");
|
||||
await inputs[3]!.setValue("cloud-secret-key-value");
|
||||
|
||||
await wrapper.find("form").trigger("submit.prevent");
|
||||
await flushPromises();
|
||||
|
||||
expect(updateSettings).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
turnstile_enabled: false,
|
||||
tencent_captcha_enabled: true,
|
||||
tencent_captcha_app_id: "123456789",
|
||||
tencent_captcha_app_secret_key: "app-secret-value",
|
||||
tencent_captcha_cloud_secret_id: "cloud-secret-id-value",
|
||||
tencent_captcha_cloud_secret_key: "cloud-secret-key-value",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("disables passkey sign-in when the RP configuration is unavailable", async () => {
|
||||
getSettings.mockResolvedValueOnce({
|
||||
...baseSettingsResponse,
|
||||
|
||||
Reference in New Issue
Block a user