update
This commit is contained in:
@@ -286,6 +286,53 @@ 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<void> => {
|
||||||
|
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)
|
* 处理用户登录请求 (POST /api/v1/auth/login)
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ import {
|
|||||||
verifyPasskeyAuthenticationHandler,
|
verifyPasskeyAuthenticationHandler,
|
||||||
// 新的 Passkey 管理处理器
|
// 新的 Passkey 管理处理器
|
||||||
listUserPasskeysHandler,
|
listUserPasskeysHandler,
|
||||||
deleteUserPasskeyHandler
|
deleteUserPasskeyHandler,
|
||||||
|
updateUserPasskeyNameHandler // 新增:更新 Passkey 名称的处理器
|
||||||
} from './auth.controller';
|
} from './auth.controller';
|
||||||
import { isAuthenticated } from './auth.middleware';
|
import { isAuthenticated } from './auth.middleware';
|
||||||
import { ipBlacklistCheckMiddleware } from './ipBlacklistCheck.middleware';
|
import { ipBlacklistCheckMiddleware } from './ipBlacklistCheck.middleware';
|
||||||
@@ -79,6 +80,9 @@ router.get('/user/passkeys', isAuthenticated, listUserPasskeysHandler);
|
|||||||
// DELETE /api/v1/auth/user/passkeys/:credentialID - 删除当前用户指定的 Passkey (需要认证)
|
// DELETE /api/v1/auth/user/passkeys/:credentialID - 删除当前用户指定的 Passkey (需要认证)
|
||||||
router.delete('/user/passkeys/:credentialID', isAuthenticated, deleteUserPasskeyHandler);
|
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 - 用户登出接口 (公开访问)
|
// POST /api/v1/auth/logout - 用户登出接口 (公开访问)
|
||||||
router.post('/logout', logout);
|
router.post('/logout', logout);
|
||||||
|
|||||||
@@ -289,6 +289,18 @@ export class PasskeyService {
|
|||||||
const wasDeleted = await this.passkeyRepo.deletePasskey(credentialID);
|
const wasDeleted = await this.passkeyRepo.deletePasskey(credentialID);
|
||||||
return wasDeleted;
|
return wasDeleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updatePasskeyName(userId: number, credentialID: string, newName: string): Promise<void> {
|
||||||
|
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);
|
export const passkeyService = new PasskeyService(passkeyRepository, userRepository);
|
||||||
@@ -13,6 +13,8 @@ export type AuditLogActionType =
|
|||||||
| 'PASSKEY_AUTH_FAILURE'
|
| 'PASSKEY_AUTH_FAILURE'
|
||||||
| 'PASSKEY_DELETED'
|
| 'PASSKEY_DELETED'
|
||||||
| 'PASSKEY_DELETE_UNAUTHORIZED'
|
| 'PASSKEY_DELETE_UNAUTHORIZED'
|
||||||
|
| 'PASSKEY_NAME_UPDATED'
|
||||||
|
| 'PASSKEY_NAME_UPDATE_UNAUTHORIZED'
|
||||||
|
|
||||||
// Connections
|
// Connections
|
||||||
| 'CONNECTION_CREATED'
|
| 'CONNECTION_CREATED'
|
||||||
|
|||||||
@@ -76,7 +76,7 @@
|
|||||||
<span class="text-xs text-text-tertiary ml-1">(ID: ...{{ typeof key.credentialID === 'string' && key.credentialID ? key.credentialID.slice(-8) : 'N/A' }})</span>
|
<span class="text-xs text-text-tertiary ml-1">(ID: ...{{ typeof key.credentialID === 'string' && key.credentialID ? key.credentialID.slice(-8) : 'N/A' }})</span>
|
||||||
</span>
|
</span>
|
||||||
<div v-else class="flex items-center flex-grow">
|
<div v-else class="flex items-center flex-grow">
|
||||||
<input type="text" v-model="editingPasskeyName" @keyup.enter="savePasskeyName(key.credentialID)" @keyup.esc="cancelEditPasskeyName" class="flex-grow px-2 py-1 border border-border rounded-md shadow-sm bg-background text-foreground focus:outline-none focus:ring-1 focus:ring-primary focus:border-primary text-sm" :placeholder="$t('settings.passkey.enterNamePlaceholder', '输入 Passkey 名称')" />
|
<input type="text" v-model="editingPasskeyName" @keyup.enter="savePasskeyName(key.credentialID)" @keyup.esc="cancelEditPasskeyName" class="flex-grow max-w-sm px-2 py-1 border border-border rounded-md shadow-sm bg-background text-foreground focus:outline-none focus:ring-1 focus:ring-primary focus:border-primary text-sm" :placeholder="$t('settings.passkey.enterNamePlaceholder', '输入 Passkey 名称')" />
|
||||||
<button @click="savePasskeyName(key.credentialID)" :disabled="passkeyEditLoadingStates[key.credentialID]" class="ml-2 px-2 py-1 bg-success text-success-text rounded-md text-xs font-medium hover:bg-success/80 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-success disabled:opacity-50 disabled:cursor-not-allowed transition duration-150 ease-in-out">
|
<button @click="savePasskeyName(key.credentialID)" :disabled="passkeyEditLoadingStates[key.credentialID]" class="ml-2 px-2 py-1 bg-success text-success-text rounded-md text-xs font-medium hover:bg-success/80 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-success disabled:opacity-50 disabled:cursor-not-allowed transition duration-150 ease-in-out">
|
||||||
{{ passkeyEditLoadingStates[key.credentialID] ? $t('common.saving') : $t('common.save') }}
|
{{ passkeyEditLoadingStates[key.credentialID] ? $t('common.saving') : $t('common.save') }}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user