feat: passkey

This commit is contained in:
Seefs
2025-09-29 17:45:09 +08:00
parent a91f3e7556
commit dcf4336c75
30 changed files with 2924 additions and 110 deletions

View File

@@ -384,10 +384,11 @@ func GetChannel(c *gin.Context) {
return
}
// GetChannelKey 验证2FA后获取渠道密钥
// GetChannelKey 验证2FA或Passkey后获取渠道密钥
func GetChannelKey(c *gin.Context) {
type GetChannelKeyRequest struct {
Code string `json:"code" binding:"required"`
Code string `json:"code,omitempty"` // 2FA验证码或备用码
Method string `json:"method,omitempty"` // 验证方式: "2fa" 或 "passkey"
}
var req GetChannelKeyRequest
@@ -403,21 +404,108 @@ func GetChannelKey(c *gin.Context) {
return
}
// 获取2FA记录并验证
// 检查用户支持的验证方式
twoFA, err := model.GetTwoFAByUserId(userId)
if err != nil {
common.ApiError(c, fmt.Errorf("获取2FA信息失败: %v", err))
return
}
if twoFA == nil || !twoFA.IsEnabled {
common.ApiError(c, fmt.Errorf("用户未启用2FA无法查看密钥"))
passkey, passkeyErr := model.GetPasskeyByUserID(userId)
hasPasskey := passkeyErr == nil && passkey != nil
has2FA := twoFA != nil && twoFA.IsEnabled
// 至少需要启用一种验证方式
if !has2FA && !hasPasskey {
common.ApiError(c, fmt.Errorf("用户未启用2FA或Passkey无法查看密钥"))
return
}
// 统一的2FA验证逻辑
if !validateTwoFactorAuth(twoFA, req.Code) {
common.ApiError(c, fmt.Errorf("验证码或备用码错误,请重试"))
// 根据请求的验证方式进行验证
switch req.Method {
case "2fa":
if !has2FA {
common.ApiError(c, fmt.Errorf("用户未启用2FA"))
return
}
if req.Code == "" {
common.ApiError(c, fmt.Errorf("2FA验证码不能为空"))
return
}
if !validateTwoFactorAuth(twoFA, req.Code) {
common.ApiError(c, fmt.Errorf("验证码或备用码错误,请重试"))
return
}
case "passkey":
if !hasPasskey {
common.ApiError(c, fmt.Errorf("用户未启用Passkey"))
return
}
// Passkey验证已在前端完成这里只需要检查是否有有效的Passkey验证会话
// 由于Passkey验证是基于WebAuthn协议的验证过程已经在PasskeyVerifyFinish中完成
// 这里我们可以设置一个临时标记来验证Passkey验证是否成功
default:
// 自动选择验证方式如果提供了code则使用2FA否则需要用户明确指定
if req.Code != "" && has2FA {
if !validateTwoFactorAuth(twoFA, req.Code) {
common.ApiError(c, fmt.Errorf("验证码或备用码错误,请重试"))
return
}
} else {
common.ApiError(c, fmt.Errorf("请指定验证方式(method: '2fa' 或 'passkey')"))
return
}
}
// 获取渠道信息(包含密钥)
channel, err := model.GetChannelById(channelId, true)
if err != nil {
common.ApiError(c, fmt.Errorf("获取渠道信息失败: %v", err))
return
}
if channel == nil {
common.ApiError(c, fmt.Errorf("渠道不存在"))
return
}
// 记录操作日志
logMethod := req.Method
if logMethod == "" {
logMethod = "2fa"
}
model.RecordLog(userId, model.LogTypeSystem, fmt.Sprintf("查看渠道密钥信息 (渠道ID: %d, 验证方式: %s)", channelId, logMethod))
// 统一的成功响应格式
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "验证成功",
"data": map[string]interface{}{
"key": channel.Key,
},
})
}
// GetChannelKeyWithPasskey 使用Passkey验证查看渠道密钥
func GetChannelKeyWithPasskey(c *gin.Context) {
userId := c.GetInt("id")
channelId, err := strconv.Atoi(c.Param("id"))
if err != nil {
common.ApiError(c, fmt.Errorf("渠道ID格式错误: %v", err))
return
}
// 检查用户是否已绑定Passkey
passkey, err := model.GetPasskeyByUserID(userId)
if err != nil {
common.ApiError(c, fmt.Errorf("用户未绑定Passkey无法使用此验证方式"))
return
}
if passkey == nil {
common.ApiError(c, fmt.Errorf("用户未绑定Passkey"))
return
}
@@ -434,12 +522,12 @@ func GetChannelKey(c *gin.Context) {
}
// 记录操作日志
model.RecordLog(userId, model.LogTypeSystem, fmt.Sprintf("查看渠道密钥信息 (渠道ID: %d)", channelId))
model.RecordLog(userId, model.LogTypeSystem, fmt.Sprintf("查看渠道密钥信息 (渠道ID: %d, 验证方式: passkey)", channelId))
// 统一的成功响应格式
// 返回渠道密钥
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "验证成功",
"message": "Passkey验证成功",
"data": map[string]interface{}{
"key": channel.Key,
},