update
This commit is contained in:
@@ -26,6 +26,16 @@ interface PublicCaptchaConfig {
|
|||||||
recaptchaSiteKey?: string;
|
recaptchaSiteKey?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Backend's full CAPTCHA Settings Interface (as returned by /settings/captcha)
|
||||||
|
interface FullCaptchaSettings {
|
||||||
|
enabled: boolean;
|
||||||
|
provider: 'hcaptcha' | 'recaptcha' | 'none';
|
||||||
|
hcaptchaSiteKey?: string;
|
||||||
|
hcaptchaSecretKey?: string; // We won't use this in authStore
|
||||||
|
recaptchaSiteKey?: string;
|
||||||
|
recaptchaSecretKey?: string; // We won't use this in authStore
|
||||||
|
}
|
||||||
|
|
||||||
// Auth Store State 接口
|
// Auth Store State 接口
|
||||||
interface AuthState {
|
interface AuthState {
|
||||||
isAuthenticated: boolean;
|
isAuthenticated: boolean;
|
||||||
@@ -281,19 +291,29 @@ export const useAuthStore = defineStore('auth', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// NEW: 获取公共 CAPTCHA 配置
|
// NEW: 获取公共 CAPTCHA 配置 (修改为从 /settings/captcha 获取)
|
||||||
async fetchCaptchaConfig() {
|
async fetchCaptchaConfig() {
|
||||||
// Avoid refetching if already loaded
|
// Avoid refetching if already loaded
|
||||||
if (this.publicCaptchaConfig !== null) return;
|
if (this.publicCaptchaConfig !== null) return;
|
||||||
|
|
||||||
// Don't set isLoading for this, it should be quick background fetch
|
// Don't set isLoading for this, it should be quick background fetch
|
||||||
try {
|
try {
|
||||||
console.log('[AuthStore] Fetching public CAPTCHA config...');
|
console.log('[AuthStore] Fetching CAPTCHA config from /settings/captcha...');
|
||||||
const response = await apiClient.get<PublicCaptchaConfig>('/auth/captcha/config');
|
// 修改 API 端点
|
||||||
this.publicCaptchaConfig = response.data;
|
const response = await apiClient.get<FullCaptchaSettings>('/settings/captcha');
|
||||||
console.log('[AuthStore] Public CAPTCHA config loaded:', this.publicCaptchaConfig);
|
const fullConfig = response.data;
|
||||||
|
|
||||||
|
// 从完整配置中提取公共部分
|
||||||
|
this.publicCaptchaConfig = {
|
||||||
|
enabled: fullConfig.enabled,
|
||||||
|
provider: fullConfig.provider,
|
||||||
|
hcaptchaSiteKey: fullConfig.hcaptchaSiteKey,
|
||||||
|
recaptchaSiteKey: fullConfig.recaptchaSiteKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('[AuthStore] Public CAPTCHA config derived from /settings/captcha:', this.publicCaptchaConfig);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('获取公共 CAPTCHA 配置失败:', error.response?.data?.message || error.message);
|
console.error('获取 CAPTCHA 配置失败 (from /settings/captcha):', error.response?.data?.message || error.message);
|
||||||
// Set a default disabled config on error to prevent blocking login UI
|
// Set a default disabled config on error to prevent blocking login UI
|
||||||
this.publicCaptchaConfig = {
|
this.publicCaptchaConfig = {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import apiClient from '../utils/apiClient'; // 使用统一的 apiClient
|
|||||||
import { ref, computed } from 'vue'; // 移除 watch
|
import { ref, computed } from 'vue'; // 移除 watch
|
||||||
import i18n, { setLocale, defaultLng } from '../i18n'; // Import i18n instance and setLocale
|
import i18n, { setLocale, defaultLng } from '../i18n'; // Import i18n instance and setLocale
|
||||||
import type { PaneName } from './layout.store'; // +++ Import PaneName type +++
|
import type { PaneName } from './layout.store'; // +++ Import PaneName type +++
|
||||||
|
import { useAuthStore } from './auth.store'; // <--- 导入 authStore
|
||||||
// Import CAPTCHA types from backend (adjust path if needed, assuming types are mirrored or shared)
|
// Import CAPTCHA types from backend (adjust path if needed, assuming types are mirrored or shared)
|
||||||
// For now, let's assume they are available via a shared types definition or manually defined here
|
// For now, let's assume they are available via a shared types definition or manually defined here
|
||||||
// Assuming manual definition for now if no shared types exist:
|
// Assuming manual definition for now if no shared types exist:
|
||||||
@@ -49,6 +50,8 @@ interface SettingsState {
|
|||||||
|
|
||||||
|
|
||||||
export const useSettingsStore = defineStore('settings', () => {
|
export const useSettingsStore = defineStore('settings', () => {
|
||||||
|
const authStore = useAuthStore(); // <--- 实例化 authStore
|
||||||
|
|
||||||
// --- State ---
|
// --- State ---
|
||||||
const settings = ref<Partial<SettingsState>>({}); // 通用设置状态
|
const settings = ref<Partial<SettingsState>>({}); // 通用设置状态
|
||||||
const parsedSidebarPaneWidths = ref<Record<string, string>>({}); // NEW: 解析后的侧边栏宽度对象
|
const parsedSidebarPaneWidths = ref<Record<string, string>>({}); // NEW: 解析后的侧边栏宽度对象
|
||||||
@@ -428,6 +431,12 @@ export const useSettingsStore = defineStore('settings', () => {
|
|||||||
}
|
}
|
||||||
console.log('[SettingsStore] CAPTCHA 设置更新成功。');
|
console.log('[SettingsStore] CAPTCHA 设置更新成功。');
|
||||||
|
|
||||||
|
// --- 新增:强制 authStore 重新获取配置 ---
|
||||||
|
console.log('[SettingsStore] Triggering authStore to refetch CAPTCHA config...');
|
||||||
|
authStore.publicCaptchaConfig = null; // 重置 authStore 的状态以允许重新获取
|
||||||
|
await authStore.fetchCaptchaConfig(); // 让 authStore 立即获取最新的配置
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('更新 CAPTCHA 设置失败:', err);
|
console.error('更新 CAPTCHA 设置失败:', err);
|
||||||
error.value = err.response?.data?.message || err.message || '更新 CAPTCHA 设置失败';
|
error.value = err.response?.data?.message || err.message || '更新 CAPTCHA 设置失败';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, ref, onMounted, computed } from 'vue'; // Import onMounted, computed
|
import { reactive, ref, onMounted, computed } from 'vue'; // computed 已导入
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useAuthStore } from '../stores/auth.store';
|
import { useAuthStore } from '../stores/auth.store';
|
||||||
@@ -25,24 +25,24 @@ const hcaptchaWidget = ref<InstanceType<typeof VueHcaptcha> | null>(null); // NE
|
|||||||
// --- reCAPTCHA v3 Initialization ---
|
// --- reCAPTCHA v3 Initialization ---
|
||||||
const recaptchaInstance = useReCaptcha(); // Get the instance, might be undefined
|
const recaptchaInstance = useReCaptcha(); // Get the instance, might be undefined
|
||||||
|
|
||||||
|
|
||||||
// --- CAPTCHA Event Handlers ---
|
// --- CAPTCHA Event Handlers ---
|
||||||
// TODO: Implement functions to handle successful CAPTCHA completion and token retrieval
|
|
||||||
const handleCaptchaVerified = (token: string) => {
|
const handleCaptchaVerified = (token: string) => {
|
||||||
console.log('CAPTCHA verified, token:', token);
|
// console.log('CAPTCHA verified, token:', token);
|
||||||
captchaToken.value = token;
|
captchaToken.value = token;
|
||||||
captchaError.value = null; // Clear error on successful verification
|
captchaError.value = null; // Clear error on successful verification
|
||||||
};
|
};
|
||||||
const handleCaptchaExpired = () => {
|
const handleCaptchaExpired = () => {
|
||||||
console.log('CAPTCHA expired');
|
// console.log('CAPTCHA expired');
|
||||||
captchaToken.value = null;
|
captchaToken.value = null;
|
||||||
};
|
};
|
||||||
const handleCaptchaError = (errorDetails: any) => {
|
const handleCaptchaError = (errorDetails: any) => {
|
||||||
console.error('CAPTCHA error:', errorDetails);
|
console.error('CAPTCHA error:', errorDetails);
|
||||||
captchaToken.value = null;
|
captchaToken.value = null;
|
||||||
captchaError.value = t('login.error.captchaLoadFailed'); // Need translation
|
captchaError.value = t('login.error.captchaLoadFailed');
|
||||||
};
|
};
|
||||||
const resetCaptchaWidget = () => {
|
const resetCaptchaWidget = () => {
|
||||||
console.log('Resetting CAPTCHA widget...');
|
// console.log('Resetting CAPTCHA widget...');
|
||||||
captchaToken.value = null;
|
captchaToken.value = null;
|
||||||
// Reset hCaptcha if it exists
|
// Reset hCaptcha if it exists
|
||||||
hcaptchaWidget.value?.reset();
|
hcaptchaWidget.value?.reset();
|
||||||
@@ -81,7 +81,7 @@ const handleSubmit = async () => {
|
|||||||
|
|
||||||
// Check if token exists (for both hCaptcha and reCAPTCHA)
|
// Check if token exists (for both hCaptcha and reCAPTCHA)
|
||||||
if (!captchaToken.value) {
|
if (!captchaToken.value) {
|
||||||
captchaError.value = t('login.error.captchaRequired'); // Need translation
|
captchaError.value = t('login.error.captchaRequired');
|
||||||
return; // Stop submission if CAPTCHA is required but not completed/obtained
|
return; // Stop submission if CAPTCHA is required but not completed/obtained
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,6 @@ const handleSubmit = async () => {
|
|||||||
resetCaptchaWidget(); // Reset the widget for potential retry
|
resetCaptchaWidget(); // Reset the widget for potential retry
|
||||||
}
|
}
|
||||||
} // <-- Correctly closing the try block here
|
} // <-- Correctly closing the try block here
|
||||||
// --- Remove the extraneous else block that was causing the syntax error ---
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fetch CAPTCHA config on component mount
|
// Fetch CAPTCHA config on component mount
|
||||||
@@ -138,6 +137,7 @@ onMounted(() => {
|
|||||||
<img src="../assets/logo.png" alt="Project Logo" class="h-16 w-auto">
|
<img src="../assets/logo.png" alt="Project Logo" class="h-16 w-auto">
|
||||||
</div>
|
</div>
|
||||||
<h2 class="text-2xl font-semibold mb-6 text-center text-foreground">{{ t('login.title') }}</h2>
|
<h2 class="text-2xl font-semibold mb-6 text-center text-foreground">{{ t('login.title') }}</h2>
|
||||||
|
|
||||||
<form @submit.prevent="handleSubmit" class="space-y-5"> <!-- Reduced space slightly -->
|
<form @submit.prevent="handleSubmit" class="space-y-5"> <!-- Reduced space slightly -->
|
||||||
<!-- Regular Login Fields -->
|
<!-- Regular Login Fields -->
|
||||||
<div v-if="!loginRequires2FA" class="space-y-6">
|
<div v-if="!loginRequires2FA" class="space-y-6">
|
||||||
@@ -167,10 +167,12 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- CAPTCHA Area -->
|
<!-- CAPTCHA Area -->
|
||||||
|
<!-- 恢复原始的 v-if 条件 -->
|
||||||
<div v-if="publicCaptchaConfig?.enabled && !loginRequires2FA" class="space-y-2">
|
<div v-if="publicCaptchaConfig?.enabled && !loginRequires2FA" class="space-y-2">
|
||||||
<label class="block text-sm font-medium text-text-secondary">{{ t('login.captchaPrompt') }}</label>
|
<label class="block text-sm font-medium text-text-secondary">{{ t('login.captchaPrompt') }}</label>
|
||||||
<!-- hCaptcha Component -->
|
<!-- hCaptcha Component -->
|
||||||
<div v-if="publicCaptchaConfig.provider === 'hcaptcha' && publicCaptchaConfig.hcaptchaSiteKey">
|
<!-- 这里的内部 v-if 仍然可以保留,虽然理论上外层已经判断过了 -->
|
||||||
|
<div v-if="publicCaptchaConfig?.provider === 'hcaptcha' && publicCaptchaConfig.hcaptchaSiteKey">
|
||||||
<VueHcaptcha
|
<VueHcaptcha
|
||||||
ref="hcaptchaWidget"
|
ref="hcaptchaWidget"
|
||||||
:sitekey="publicCaptchaConfig.hcaptchaSiteKey"
|
:sitekey="publicCaptchaConfig.hcaptchaSiteKey"
|
||||||
@@ -181,9 +183,9 @@ onMounted(() => {
|
|||||||
></VueHcaptcha>
|
></VueHcaptcha>
|
||||||
</div>
|
</div>
|
||||||
<!-- reCAPTCHA v3 Info (usually invisible) -->
|
<!-- reCAPTCHA v3 Info (usually invisible) -->
|
||||||
<div v-else-if="publicCaptchaConfig.provider === 'recaptcha'">
|
<div v-else-if="publicCaptchaConfig?.provider === 'recaptcha'">
|
||||||
<p class="text-xs text-text-secondary italic">
|
<p class="text-xs text-text-secondary italic">
|
||||||
{{ t('login.recaptchaV3Notice', 'This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.') }}
|
{{ t('login.recaptchaV3Notice') }}
|
||||||
</p>
|
</p>
|
||||||
<!-- v3 is typically invisible, token obtained programmatically on submit -->
|
<!-- v3 is typically invisible, token obtained programmatically on submit -->
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user