diff --git a/packages/backend/src/auth/auth.controller.ts b/packages/backend/src/auth/auth.controller.ts index fa7df90..fb30453 100644 --- a/packages/backend/src/auth/auth.controller.ts +++ b/packages/backend/src/auth/auth.controller.ts @@ -286,7 +286,54 @@ export const deleteUserPasskeyHandler = async (req: Request, res: Response): Pro } }; +/** + * 更新当前认证用户指定的 Passkey 名称 (PUT /api/v1/user/passkeys/:credentialID/name) + */ +export const updateUserPasskeyNameHandler = async (req: Request, res: Response): Promise => { + const userId = req.session.userId; + const username = req.session.username; + const { credentialID } = req.params; + const { name } = req.body; + if (!userId || !username) { + res.status(401).json({ message: '用户未认证。' }); + return; + } + + if (!credentialID) { + res.status(400).json({ message: '必须提供 Passkey 的 CredentialID。' }); + return; + } + + if (typeof name !== 'string' || name.trim() === '') { + res.status(400).json({ message: 'Passkey 名称不能为空。' }); + return; + } + + const trimmedName = name.trim(); + + try { + await passkeyService.updatePasskeyName(userId, credentialID, trimmedName); + console.log(`[AuthController] 用户 ${username} (ID: ${userId}) 成功更新了 Passkey (CredentialID: ${credentialID}) 的名称为 "${trimmedName}"。`); + auditLogService.logAction('PASSKEY_NAME_UPDATED', { userId, username, credentialId: credentialID, newName: trimmedName }); + // Optionally send a notification if desired + // notificationService.sendNotification('PASSKEY_NAME_UPDATED', { userId, username, credentialId: credentialID, newName: trimmedName }); + res.status(200).json({ message: 'Passkey 名称更新成功。' }); + + } catch (error: any) { + console.error(`[AuthController] 用户 ${username} (ID: ${userId}) 更新 Passkey (CredentialID: ${credentialID}) 名称时出错:`, error.message, error.stack); + if (error.message === 'Passkey not found.') { + res.status(404).json({ message: '指定的 Passkey 未找到。' }); + } else if (error.message === 'Unauthorized to update this passkey name.') { + auditLogService.logAction('PASSKEY_NAME_UPDATE_UNAUTHORIZED', { userId, username, credentialIdAttempted: credentialID }); + res.status(403).json({ message: '无权更新此 Passkey 名称。' }); + } else { + res.status(500).json({ message: '更新 Passkey 名称失败。', error: error.message }); + } + } +}; + + /** * 处理用户登录请求 (POST /api/v1/auth/login) */ diff --git a/packages/backend/src/auth/auth.routes.ts b/packages/backend/src/auth/auth.routes.ts index 925fd23..6e61060 100644 --- a/packages/backend/src/auth/auth.routes.ts +++ b/packages/backend/src/auth/auth.routes.ts @@ -18,7 +18,8 @@ import { verifyPasskeyAuthenticationHandler, // 新的 Passkey 管理处理器 listUserPasskeysHandler, - deleteUserPasskeyHandler + deleteUserPasskeyHandler, + updateUserPasskeyNameHandler // 新增:更新 Passkey 名称的处理器 } from './auth.controller'; import { isAuthenticated } from './auth.middleware'; import { ipBlacklistCheckMiddleware } from './ipBlacklistCheck.middleware'; @@ -79,7 +80,10 @@ router.get('/user/passkeys', isAuthenticated, listUserPasskeysHandler); // DELETE /api/v1/auth/user/passkeys/:credentialID - 删除当前用户指定的 Passkey (需要认证) router.delete('/user/passkeys/:credentialID', isAuthenticated, deleteUserPasskeyHandler); - +// PUT /api/v1/auth/user/passkeys/:credentialID/name - 更新当前用户指定的 Passkey 名称 (需要认证) +router.put('/user/passkeys/:credentialID/name', isAuthenticated, updateUserPasskeyNameHandler); + + // POST /api/v1/auth/logout - 用户登出接口 (公开访问) router.post('/logout', logout); diff --git a/packages/backend/src/services/passkey.service.ts b/packages/backend/src/services/passkey.service.ts index 1b3bf07..44bb917 100644 --- a/packages/backend/src/services/passkey.service.ts +++ b/packages/backend/src/services/passkey.service.ts @@ -289,6 +289,18 @@ export class PasskeyService { const wasDeleted = await this.passkeyRepo.deletePasskey(credentialID); return wasDeleted; } -} + async updatePasskeyName(userId: number, credentialID: string, newName: string): Promise { + const passkey = await this.passkeyRepo.getPasskeyByCredentialId(credentialID); + if (!passkey) { + throw new Error('Passkey not found.'); + } + if (passkey.user_id !== userId) { + // Security measure: User can only update their own passkey names + throw new Error('Unauthorized to update this passkey name.'); + } + await this.passkeyRepo.updatePasskeyName(credentialID, newName); + } +} + export const passkeyService = new PasskeyService(passkeyRepository, userRepository); \ No newline at end of file diff --git a/packages/backend/src/types/audit.types.ts b/packages/backend/src/types/audit.types.ts index af4cada..116c821 100644 --- a/packages/backend/src/types/audit.types.ts +++ b/packages/backend/src/types/audit.types.ts @@ -13,7 +13,9 @@ export type AuditLogActionType = | 'PASSKEY_AUTH_FAILURE' | 'PASSKEY_DELETED' | 'PASSKEY_DELETE_UNAUTHORIZED' - + | 'PASSKEY_NAME_UPDATED' + | 'PASSKEY_NAME_UPDATE_UNAUTHORIZED' + // Connections | 'CONNECTION_CREATED' | 'CONNECTION_UPDATED' diff --git a/packages/frontend/src/views/SettingsView.vue b/packages/frontend/src/views/SettingsView.vue index 982cf89..6c1c82a 100644 --- a/packages/frontend/src/views/SettingsView.vue +++ b/packages/frontend/src/views/SettingsView.vue @@ -76,7 +76,7 @@ (ID: ...{{ typeof key.credentialID === 'string' && key.credentialID ? key.credentialID.slice(-8) : 'N/A' }})
- +