feat: 添加CAPTCHA验证

This commit is contained in:
Baobhan Sith
2025-05-11 19:58:49 +08:00
parent 12260681b7
commit 7ee8ffb90a
9 changed files with 298 additions and 46 deletions
+4 -1
View File
@@ -808,7 +808,10 @@
"saved": "CAPTCHA settings saved successfully."
},
"error": {
"saveFailed": "Failed to save CAPTCHA settings."
"saveFailed": "Failed to save CAPTCHA settings.",
"hcaptchaKeysRequired": "hCaptcha Site Key and Secret Key are required for verification.",
"recaptchaKeysRequired": "reCAPTCHA Site Key and Secret Key are required for verification.",
"verificationFailed": "CAPTCHA credential verification failed. Please check your Site Key and Secret Key."
}
},
"commandInputSync": {
+4 -1
View File
@@ -691,7 +691,10 @@
"description": "自動化された攻撃を防ぐために、ログインページに CAPTCHA 検証を設定します。",
"enableLabel": "ログインページで CAPTCHA を有効にする",
"error": {
"saveFailed": "CAPTCHA 設定の保存に失敗しました。"
"saveFailed": "CAPTCHA 設定の保存に失敗しました。",
"hcaptchaKeysRequired": "hCaptcha サイトキーとシークレットキーは検証に必要です。",
"recaptchaKeysRequired": "reCAPTCHA サイトキーとシークレットキーは検証に必要です。",
"verificationFailed": "CAPTCHA 認証情報検証に失敗しました。サイトキーとシークレットキーを確認してください。"
},
"hcaptchaHint": "から",
"providerLabel": "CAPTCHA プロバイダー:",
+4 -1
View File
@@ -807,7 +807,10 @@
"saved": "CAPTCHA 设置已成功保存。"
},
"error": {
"saveFailed": "保存 CAPTCHA 设置失败。"
"saveFailed": "保存 CAPTCHA 设置失败。",
"hcaptchaKeysRequired": "hCaptcha Site Key 和 Secret Key 是必填项,以便进行验证。",
"recaptchaKeysRequired": "reCAPTCHA Site Key 和 Secret Key 是必填项,以便进行验证。",
"verificationFailed": "CAPTCHA 凭据验证失败。请检查您输入的 Site Key 和 Secret Key 是否正确。"
}
},
"commandInputSync": {
+1 -1
View File
@@ -12,7 +12,7 @@ interface UserInfo {
}
// Passkey Information Interface
interface PasskeyInfo {
export interface PasskeyInfo { // + Export 接口
credentialID: string;
publicKey: string; // Or a more specific type if available
counter: number;
+111 -18
View File
@@ -1654,32 +1654,125 @@ const handleUpdateCaptchaSettings = async () => {
captchaMessage.value = '';
captchaSuccess.value = false;
try {
// Prepare DTO, only sending secret keys if they have been entered
const dto: UpdateCaptchaSettingsDto = {
let needsVerification = false;
let providerForVerification: CaptchaProvider | null = null;
let siteKeyForVerification: string | undefined = undefined;
let secretKeyForVerification: string | undefined = undefined;
// 步骤 1: 确定是否需要验证
if (captchaForm.enabled && captchaForm.provider && captchaForm.provider !== 'none') {
const originalSettings = captchaSettings.value; // 从 store 获取的持久化设置
if (captchaForm.provider === 'hcaptcha') {
const originalSiteKeyValue = originalSettings?.hcaptchaSiteKey || '';
const currentSiteKeyValue = captchaForm.hcaptchaSiteKey || '';
const currentSecretKeyValue = captchaForm.hcaptchaSecretKey || ''; // 用户在表单中输入的新 secret
if (currentSiteKeyValue !== originalSiteKeyValue) { // 情况 A: 站点密钥已更改
if (!currentSiteKeyValue || !currentSecretKeyValue) {
captchaMessage.value = t('settings.captcha.error.hcaptchaKeysRequired');
captchaSuccess.value = false;
captchaLoading.value = false;
return;
}
needsVerification = true;
providerForVerification = 'hcaptcha';
siteKeyForVerification = currentSiteKeyValue;
secretKeyForVerification = currentSecretKeyValue;
} else if (currentSecretKeyValue) { // 情况 B: 站点密钥未更改,但用户输入了新的秘密密钥
if (!currentSiteKeyValue) { // 确保站点密钥本身不为空
captchaMessage.value = t('settings.captcha.error.hcaptchaKeysRequired');
captchaSuccess.value = false;
captchaLoading.value = false;
return;
}
needsVerification = true;
providerForVerification = 'hcaptcha';
siteKeyForVerification = currentSiteKeyValue; // 使用当前的 (也是原始的) 站点密钥
secretKeyForVerification = currentSecretKeyValue;
}
// 情况 C: 站点密钥未更改,且用户未输入新的秘密密钥 (currentSecretKeyValue is empty) -> needsVerification 保持 false
} else if (captchaForm.provider === 'recaptcha') {
const originalSiteKeyValue = originalSettings?.recaptchaSiteKey || '';
const currentSiteKeyValue = captchaForm.recaptchaSiteKey || '';
const currentSecretKeyValue = captchaForm.recaptchaSecretKey || '';
if (currentSiteKeyValue !== originalSiteKeyValue) { // 情况 A: 站点密钥已更改
if (!currentSiteKeyValue || !currentSecretKeyValue) {
captchaMessage.value = t('settings.captcha.error.recaptchaKeysRequired');
captchaSuccess.value = false;
captchaLoading.value = false;
return;
}
needsVerification = true;
providerForVerification = 'recaptcha';
siteKeyForVerification = currentSiteKeyValue;
secretKeyForVerification = currentSecretKeyValue;
} else if (currentSecretKeyValue) { // 情况 B: 站点密钥未更改,但用户输入了新的秘密密钥
if (!currentSiteKeyValue) { // 确保站点密钥本身不为空
captchaMessage.value = t('settings.captcha.error.recaptchaKeysRequired');
captchaSuccess.value = false;
captchaLoading.value = false;
return;
}
needsVerification = true;
providerForVerification = 'recaptcha';
siteKeyForVerification = currentSiteKeyValue;
secretKeyForVerification = currentSecretKeyValue;
}
// 情况 C: 站点密钥未更改,且用户未输入新的秘密密钥 -> needsVerification 保持 false
}
}
// 步骤 2: 如果需要,执行验证
if (needsVerification && providerForVerification && siteKeyForVerification && secretKeyForVerification) {
try {
await apiClient.post('/settings/captcha/verify', {
provider: providerForVerification,
siteKey: siteKeyForVerification,
secretKey: secretKeyForVerification,
});
// 验证成功,可以继续
} catch (verifyError: any) {
console.error('CAPTCHA 验证失败:', verifyError);
captchaMessage.value = verifyError.response?.data?.message || verifyError.message || t('settings.captcha.error.verificationFailed');
captchaSuccess.value = false;
captchaLoading.value = false;
return; // 验证失败,不继续保存
}
}
// 步骤 3: 准备用于保存的 DTO
const dtoToSave: UpdateCaptchaSettingsDto = {
enabled: captchaForm.enabled,
provider: captchaForm.provider,
// Site keys are not sensitive, send them if present
hcaptchaSiteKey: captchaForm.hcaptchaSiteKey || '',
recaptchaSiteKey: captchaForm.recaptchaSiteKey || '',
// Site keys 总是从表单获取
hcaptchaSiteKey: captchaForm.hcaptchaSiteKey || undefined,
recaptchaSiteKey: captchaForm.recaptchaSiteKey || undefined,
// Secret keys 仅在表单中提供时才包含
hcaptchaSecretKey: captchaForm.hcaptchaSecretKey || undefined,
recaptchaSecretKey: captchaForm.recaptchaSecretKey || undefined,
};
// 如果 captchaForm.provider 为 'none' 或 captchaForm.enabled 为 false,
// 后端应负责清除所有相关的 site/secret key。
// 如果表单中的 secret key 为空字符串,则发送 undefined,后端不应更新该特定 secret key。
// Only include secret keys in the DTO if the user entered a value
if (captchaForm.hcaptchaSecretKey) {
dto.hcaptchaSecretKey = captchaForm.hcaptchaSecretKey;
}
if (captchaForm.recaptchaSecretKey) {
dto.recaptchaSecretKey = captchaForm.recaptchaSecretKey;
}
await settingsStore.updateCaptchaSettings(dto);
captchaMessage.value = t('settings.captcha.success.saved'); // Need translation
// 步骤 4: 调用保存操作
await settingsStore.updateCaptchaSettings(dtoToSave);
captchaMessage.value = t('settings.captcha.success.saved');
captchaSuccess.value = true;
// Clear secret key fields in the form after successful save for security
// 成功保存后清除表单中的 secret key 字段,以确保下次编辑时它们是空的,除非用户再次输入
captchaForm.hcaptchaSecretKey = '';
captchaForm.recaptchaSecretKey = '';
} catch (error: any) {
console.error('更新 CAPTCHA 设置失败:', error);
captchaMessage.value = error.message || t('settings.captcha.error.saveFailed'); // Need translation
// 此 catch 块处理来自 settingsStore.updateCaptchaSettings 的错误
// 或在 try 块中未被 'return' 语句捕获的其他错误。
console.error('更新 CAPTCHA 设置时捕获到错误:', error);
// 避免覆盖更具体的错误消息(例如,来自验证失败的消息)
if (!captchaMessage.value) {
captchaMessage.value = error.message || t('settings.captcha.error.saveFailed');
}
captchaSuccess.value = false;
} finally {
captchaLoading.value = false;