secure_verification.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. package controller
  2. import (
  3. "fmt"
  4. "net/http"
  5. "time"
  6. "github.com/QuantumNous/new-api/common"
  7. "github.com/QuantumNous/new-api/model"
  8. "github.com/gin-contrib/sessions"
  9. "github.com/gin-gonic/gin"
  10. )
  11. const (
  12. // SecureVerificationSessionKey means the user has fully passed secure verification.
  13. SecureVerificationSessionKey = "secure_verified_at"
  14. // PasskeyReadySessionKey means WebAuthn finished and /api/verify can finalize step-up verification.
  15. PasskeyReadySessionKey = "secure_passkey_ready_at"
  16. // SecureVerificationTimeout 验证有效期(秒)
  17. SecureVerificationTimeout = 300 // 5分钟
  18. // PasskeyReadyTimeout passkey ready 标记有效期(秒)
  19. PasskeyReadyTimeout = 60
  20. )
  21. type UniversalVerifyRequest struct {
  22. Method string `json:"method"` // "2fa" 或 "passkey"
  23. Code string `json:"code,omitempty"`
  24. }
  25. type VerificationStatusResponse struct {
  26. Verified bool `json:"verified"`
  27. ExpiresAt int64 `json:"expires_at,omitempty"`
  28. }
  29. // UniversalVerify 通用验证接口
  30. // 支持 2FA 和 Passkey 验证,验证成功后在 session 中记录时间戳
  31. func UniversalVerify(c *gin.Context) {
  32. userId := c.GetInt("id")
  33. if userId == 0 {
  34. c.JSON(http.StatusUnauthorized, gin.H{
  35. "success": false,
  36. "message": "未登录",
  37. })
  38. return
  39. }
  40. var req UniversalVerifyRequest
  41. if err := c.ShouldBindJSON(&req); err != nil {
  42. common.ApiError(c, fmt.Errorf("参数错误: %v", err))
  43. return
  44. }
  45. // 获取用户信息
  46. user := &model.User{Id: userId}
  47. if err := user.FillUserById(); err != nil {
  48. common.ApiError(c, fmt.Errorf("获取用户信息失败: %v", err))
  49. return
  50. }
  51. if user.Status != common.UserStatusEnabled {
  52. common.ApiError(c, fmt.Errorf("该用户已被禁用"))
  53. return
  54. }
  55. // 检查用户的验证方式
  56. twoFA, _ := model.GetTwoFAByUserId(userId)
  57. has2FA := twoFA != nil && twoFA.IsEnabled
  58. passkey, passkeyErr := model.GetPasskeyByUserID(userId)
  59. hasPasskey := passkeyErr == nil && passkey != nil
  60. if !has2FA && !hasPasskey {
  61. common.ApiError(c, fmt.Errorf("用户未启用2FA或Passkey"))
  62. return
  63. }
  64. // 根据验证方式进行验证
  65. var verified bool
  66. var verifyMethod string
  67. var err error
  68. switch req.Method {
  69. case "2fa":
  70. if !has2FA {
  71. common.ApiError(c, fmt.Errorf("用户未启用2FA"))
  72. return
  73. }
  74. if req.Code == "" {
  75. common.ApiError(c, fmt.Errorf("验证码不能为空"))
  76. return
  77. }
  78. verified = validateTwoFactorAuth(twoFA, req.Code)
  79. verifyMethod = "2FA"
  80. case "passkey":
  81. if !hasPasskey {
  82. common.ApiError(c, fmt.Errorf("用户未启用Passkey"))
  83. return
  84. }
  85. // Passkey branch only trusts the short-lived marker written by PasskeyVerifyFinish.
  86. verified, err = consumePasskeyReady(c)
  87. if err != nil {
  88. common.ApiError(c, fmt.Errorf("Passkey 验证状态异常: %v", err))
  89. return
  90. }
  91. if !verified {
  92. common.ApiError(c, fmt.Errorf("请先完成 Passkey 验证"))
  93. return
  94. }
  95. verifyMethod = "Passkey"
  96. default:
  97. common.ApiError(c, fmt.Errorf("不支持的验证方式: %s", req.Method))
  98. return
  99. }
  100. if !verified {
  101. common.ApiError(c, fmt.Errorf("验证失败,请检查验证码"))
  102. return
  103. }
  104. // 验证成功,在 session 中记录时间戳
  105. now, err := setSecureVerificationSession(c)
  106. if err != nil {
  107. common.ApiError(c, fmt.Errorf("保存验证状态失败: %v", err))
  108. return
  109. }
  110. // 记录日志
  111. model.RecordLog(userId, model.LogTypeSystem, fmt.Sprintf("通用安全验证成功 (验证方式: %s)", verifyMethod))
  112. c.JSON(http.StatusOK, gin.H{
  113. "success": true,
  114. "message": "验证成功",
  115. "data": gin.H{
  116. "verified": true,
  117. "expires_at": now + SecureVerificationTimeout,
  118. },
  119. })
  120. }
  121. func setSecureVerificationSession(c *gin.Context) (int64, error) {
  122. session := sessions.Default(c)
  123. session.Delete(PasskeyReadySessionKey)
  124. now := time.Now().Unix()
  125. session.Set(SecureVerificationSessionKey, now)
  126. if err := session.Save(); err != nil {
  127. return 0, err
  128. }
  129. return now, nil
  130. }
  131. func consumePasskeyReady(c *gin.Context) (bool, error) {
  132. session := sessions.Default(c)
  133. readyAtRaw := session.Get(PasskeyReadySessionKey)
  134. if readyAtRaw == nil {
  135. return false, nil
  136. }
  137. readyAt, ok := readyAtRaw.(int64)
  138. if !ok {
  139. session.Delete(PasskeyReadySessionKey)
  140. _ = session.Save()
  141. return false, fmt.Errorf("无效的 Passkey 验证状态")
  142. }
  143. session.Delete(PasskeyReadySessionKey)
  144. if err := session.Save(); err != nil {
  145. return false, err
  146. }
  147. // Expired ready markers cannot be reused.
  148. if time.Now().Unix()-readyAt >= PasskeyReadyTimeout {
  149. return false, nil
  150. }
  151. return true, nil
  152. }