From 787114bb4c66a26c509db1e4009aec9311d5dc12 Mon Sep 17 00:00:00 2001 From: Baobhan Sith <80159437+Heavrnl@users.noreply.github.com> Date: Thu, 8 May 2025 16:43:02 +0800 Subject: [PATCH] update --- packages/backend/src/auth/auth.controller.ts | 17 +++++++++++++++++ packages/backend/src/auth/auth.routes.ts | 7 ++++++- .../src/repositories/passkey.repository.ts | 7 +++++++ .../backend/src/services/passkey.service.ts | 16 ++++++++++++++++ packages/frontend/src/stores/auth.store.ts | 19 +++++++++++++++++++ packages/frontend/src/views/LoginView.vue | 13 ++++++++----- packages/frontend/src/views/SettingsView.vue | 2 +- 7 files changed, 74 insertions(+), 7 deletions(-) diff --git a/packages/backend/src/auth/auth.controller.ts b/packages/backend/src/auth/auth.controller.ts index fb30453..6574c87 100644 --- a/packages/backend/src/auth/auth.controller.ts +++ b/packages/backend/src/auth/auth.controller.ts @@ -921,3 +921,20 @@ export const getPublicCaptchaConfig = async (req: Request, res: Response): Promi }); } }; + +/** + * 检查系统中是否配置了任何 Passkey (GET /api/v1/auth/passkey/has-configured) + * 或者特定用户是否配置了 Passkey (GET /api/v1/auth/passkey/has-configured?username=xxx) + * 公开访问,用于登录页面判断是否显示 Passkey 登录按钮。 + */ +export const checkHasPasskeys = async (req: Request, res: Response): Promise => { + const username = req.query.username as string | undefined; + try { + const hasPasskeys = await passkeyService.hasPasskeysConfigured(username); + res.status(200).json({ hasPasskeys }); + } catch (error: any) { + console.error(`[AuthController] 检查 Passkey 配置状态时出错 (username: ${username || 'any'}):`, error.message); + // 即使出错,也返回 false,避免登录流程中断 + res.status(200).json({ hasPasskeys: false, error: '检查 Passkey 配置时出错。' }); + } +}; diff --git a/packages/backend/src/auth/auth.routes.ts b/packages/backend/src/auth/auth.routes.ts index 6e61060..eb0d9b3 100644 --- a/packages/backend/src/auth/auth.routes.ts +++ b/packages/backend/src/auth/auth.routes.ts @@ -19,7 +19,8 @@ import { // 新的 Passkey 管理处理器 listUserPasskeysHandler, deleteUserPasskeyHandler, - updateUserPasskeyNameHandler // 新增:更新 Passkey 名称的处理器 + updateUserPasskeyNameHandler, // 新增:更新 Passkey 名称的处理器 + checkHasPasskeys // +++ 新增:检查是否有 Passkey 配置的处理器 } from './auth.controller'; import { isAuthenticated } from './auth.middleware'; import { ipBlacklistCheckMiddleware } from './ipBlacklistCheck.middleware'; @@ -70,9 +71,13 @@ router.post('/passkey/register', isAuthenticated, verifyPasskeyRegistrationHandl // POST /api/v1/auth/passkey/authentication-options - 生成 Passkey 认证选项 (公开或半公开,取决于是否提供了用户名) router.post('/passkey/authentication-options', generatePasskeyAuthenticationOptionsHandler); + // POST /api/v1/auth/passkey/authenticate - 验证 Passkey 并登录用户 (公开) router.post('/passkey/authenticate', ipBlacklistCheckMiddleware, verifyPasskeyAuthenticationHandler); +// GET /api/v1/auth/passkey/has-configured - 检查是否配置了 Passkey (公开) +router.get('/passkey/has-configured', checkHasPasskeys); + // --- User's Passkey Management Routes (New) --- // GET /api/v1/auth/user/passkeys - 获取当前用户的所有 Passkey (需要认证) router.get('/user/passkeys', isAuthenticated, listUserPasskeysHandler); diff --git a/packages/backend/src/repositories/passkey.repository.ts b/packages/backend/src/repositories/passkey.repository.ts index 5d9d059..7fe80ed 100644 --- a/packages/backend/src/repositories/passkey.repository.ts +++ b/packages/backend/src/repositories/passkey.repository.ts @@ -142,6 +142,13 @@ export class PasskeyRepository { const { changes } = await runDb(db, sql, [name, credentialId]); return changes > 0; } + + async getFirstPasskey(): Promise { + const db = await getDbInstance(); + const sql = 'SELECT * FROM passkeys LIMIT 1'; + const result = await getDb(db, sql); + return mapPasskeyResult(result); + } } export const passkeyRepository = new PasskeyRepository(); diff --git a/packages/backend/src/services/passkey.service.ts b/packages/backend/src/services/passkey.service.ts index 46e342e..7b5d2dd 100644 --- a/packages/backend/src/services/passkey.service.ts +++ b/packages/backend/src/services/passkey.service.ts @@ -301,6 +301,22 @@ export class PasskeyService { } await this.passkeyRepo.updatePasskeyName(credentialID, newName); } + + async hasPasskeysConfigured(username?: string): Promise { + if (username) { + const user = await this.userRepo.findUserByUsername(username); + if (!user) { + return false; // 如果提供了用户名但用户不存在,则认为没有配置 passkey + } + const passkeys = await this.passkeyRepo.getPasskeysByUserId(user.id); + return passkeys.length > 0; + } else { + // 如果没有提供用户名,检查整个系统中是否存在任何 passkey + // 这对于“可发现凭证”场景可能有用,或者简单地检查系统是否启用了 passkey 功能 + const anyPasskey = await this.passkeyRepo.getFirstPasskey(); + return !!anyPasskey; + } + } } export const passkeyService = new PasskeyService(passkeyRepository, userRepository); \ No newline at end of file diff --git a/packages/frontend/src/stores/auth.store.ts b/packages/frontend/src/stores/auth.store.ts index 5df85a9..557a207 100644 --- a/packages/frontend/src/stores/auth.store.ts +++ b/packages/frontend/src/stores/auth.store.ts @@ -65,6 +65,7 @@ interface AuthState { publicCaptchaConfig: PublicCaptchaConfig | null; // NEW: Public CAPTCHA config passkeys: PasskeyInfo[] | null; // NEW: Store for user's passkeys passkeysLoading: boolean; // NEW: Loading state for passkeys + hasPasskeysAvailable: boolean; // NEW: Indicates if passkeys are available for login } export const useAuthStore = defineStore('auth', { @@ -79,6 +80,7 @@ export const useAuthStore = defineStore('auth', { publicCaptchaConfig: null, // NEW: Initialize CAPTCHA config as null passkeys: null, // Initialize passkeys as null passkeysLoading: false, // Initialize passkeysLoading as false + hasPasskeysAvailable: false, // Initialize as false }), getters: { // 可以添加一些 getter,例如获取用户名 @@ -505,6 +507,23 @@ export const useAuthStore = defineStore('auth', { // if using specific loading state: this.passkeyNameUpdateLoading = false; } }, + + // Action to check if passkeys are configured (for login page) + async checkHasPasskeysConfigured(username?: string) { + // This action should not set isLoading to true, as it's a quick check + // and primarily used to determine UI elements on the login page. + try { + const params = username ? { username } : {}; + const response = await apiClient.get<{ hasPasskeys: boolean }>('/auth/passkey/has-configured', { params }); + this.hasPasskeysAvailable = response.data.hasPasskeys; + console.log(`[AuthStore] Passkeys available for ${username || 'any user'}: ${this.hasPasskeysAvailable}`); + return this.hasPasskeysAvailable; + } catch (error: any) { + console.error('Failed to check if passkeys are configured:', error.response?.data?.message || error.message); + this.hasPasskeysAvailable = false; // Default to false on error + return false; + } + }, }, persist: true, // Revert to simple persistence to fix TS error for now }); diff --git a/packages/frontend/src/views/LoginView.vue b/packages/frontend/src/views/LoginView.vue index 0656ccf..99afd67 100644 --- a/packages/frontend/src/views/LoginView.vue +++ b/packages/frontend/src/views/LoginView.vue @@ -10,7 +10,7 @@ import VueRecaptcha from 'vue3-recaptcha2'; // 使用默认导入 const { t } = useI18n(); const authStore = useAuthStore(); // 获取 loginRequires2FA 状态 -const { isLoading, error, loginRequires2FA, publicCaptchaConfig, passkeys } = storeToRefs(authStore); // Get publicCaptchaConfig and passkeys +const { isLoading, error, loginRequires2FA, publicCaptchaConfig, hasPasskeysAvailable } = storeToRefs(authStore); // Get publicCaptchaConfig and hasPasskeysAvailable // 表单数据 const credentials = reactive({ @@ -92,10 +92,13 @@ const handleSubmit = async () => { } // <-- Correctly closing the try block here }; -// Fetch CAPTCHA config on component mount -onMounted(() => { - // console.log('[LoginView] Component mounted, calling fetchCaptchaConfig...'); // 添加日志 + // Fetch CAPTCHA config and check passkey availability on component mount +onMounted(async () => { + // console.log('[LoginView] Component mounted, calling fetchCaptchaConfig and checkHasPasskeysConfigured...'); authStore.fetchCaptchaConfig(); + // Check if passkeys are available for login (uses the new public endpoint) + // Optionally pass username if needed: await authStore.checkHasPasskeysConfigured(credentials.username); + await authStore.checkHasPasskeysConfigured(); }); // --- Passkey Login Handler --- @@ -243,7 +246,7 @@ const handlePasskeyLogin = async () => { -
+