twofa.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. package model
  2. import (
  3. "errors"
  4. "fmt"
  5. "time"
  6. "github.com/QuantumNous/new-api/common"
  7. "gorm.io/gorm"
  8. )
  9. var ErrTwoFANotEnabled = errors.New("用户未启用2FA")
  10. // TwoFA 用户2FA设置表
  11. type TwoFA struct {
  12. Id int `json:"id" gorm:"primaryKey"`
  13. UserId int `json:"user_id" gorm:"unique;not null;index"`
  14. Secret string `json:"-" gorm:"type:varchar(255);not null"` // TOTP密钥,不返回给前端
  15. IsEnabled bool `json:"is_enabled"`
  16. FailedAttempts int `json:"failed_attempts" gorm:"default:0"`
  17. LockedUntil *time.Time `json:"locked_until,omitempty"`
  18. LastUsedAt *time.Time `json:"last_used_at,omitempty"`
  19. CreatedAt time.Time `json:"created_at"`
  20. UpdatedAt time.Time `json:"updated_at"`
  21. DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
  22. }
  23. // TwoFABackupCode 备用码使用记录表
  24. type TwoFABackupCode struct {
  25. Id int `json:"id" gorm:"primaryKey"`
  26. UserId int `json:"user_id" gorm:"not null;index"`
  27. CodeHash string `json:"-" gorm:"type:varchar(255);not null"` // 备用码哈希
  28. IsUsed bool `json:"is_used"`
  29. UsedAt *time.Time `json:"used_at,omitempty"`
  30. CreatedAt time.Time `json:"created_at"`
  31. DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
  32. }
  33. // GetTwoFAByUserId 根据用户ID获取2FA设置
  34. func GetTwoFAByUserId(userId int) (*TwoFA, error) {
  35. if userId == 0 {
  36. return nil, errors.New("用户ID不能为空")
  37. }
  38. var twoFA TwoFA
  39. err := DB.Where("user_id = ?", userId).First(&twoFA).Error
  40. if err != nil {
  41. if errors.Is(err, gorm.ErrRecordNotFound) {
  42. return nil, nil // 返回nil表示未设置2FA
  43. }
  44. return nil, err
  45. }
  46. return &twoFA, nil
  47. }
  48. // IsTwoFAEnabled 检查用户是否启用了2FA
  49. func IsTwoFAEnabled(userId int) bool {
  50. twoFA, err := GetTwoFAByUserId(userId)
  51. if err != nil || twoFA == nil {
  52. return false
  53. }
  54. return twoFA.IsEnabled
  55. }
  56. // CreateTwoFA 创建2FA设置
  57. func (t *TwoFA) Create() error {
  58. // 检查用户是否已存在2FA设置
  59. existing, err := GetTwoFAByUserId(t.UserId)
  60. if err != nil {
  61. return err
  62. }
  63. if existing != nil {
  64. return errors.New("用户已存在2FA设置")
  65. }
  66. // 验证用户存在
  67. var user User
  68. if err := DB.First(&user, t.UserId).Error; err != nil {
  69. if errors.Is(err, gorm.ErrRecordNotFound) {
  70. return errors.New("用户不存在")
  71. }
  72. return err
  73. }
  74. return DB.Create(t).Error
  75. }
  76. // Update 更新2FA设置
  77. func (t *TwoFA) Update() error {
  78. if t.Id == 0 {
  79. return errors.New("2FA记录ID不能为空")
  80. }
  81. return DB.Save(t).Error
  82. }
  83. // Delete 删除2FA设置
  84. func (t *TwoFA) Delete() error {
  85. if t.Id == 0 {
  86. return errors.New("2FA记录ID不能为空")
  87. }
  88. // 使用事务确保原子性
  89. return DB.Transaction(func(tx *gorm.DB) error {
  90. // 同时删除相关的备用码记录(硬删除)
  91. if err := tx.Unscoped().Where("user_id = ?", t.UserId).Delete(&TwoFABackupCode{}).Error; err != nil {
  92. return err
  93. }
  94. // 硬删除2FA记录
  95. return tx.Unscoped().Delete(t).Error
  96. })
  97. }
  98. // ResetFailedAttempts 重置失败尝试次数
  99. func (t *TwoFA) ResetFailedAttempts() error {
  100. t.FailedAttempts = 0
  101. t.LockedUntil = nil
  102. return t.Update()
  103. }
  104. // IncrementFailedAttempts 增加失败尝试次数
  105. func (t *TwoFA) IncrementFailedAttempts() error {
  106. t.FailedAttempts++
  107. // 检查是否需要锁定
  108. if t.FailedAttempts >= common.MaxFailAttempts {
  109. lockUntil := time.Now().Add(time.Duration(common.LockoutDuration) * time.Second)
  110. t.LockedUntil = &lockUntil
  111. }
  112. return t.Update()
  113. }
  114. // IsLocked 检查账户是否被锁定
  115. func (t *TwoFA) IsLocked() bool {
  116. if t.LockedUntil == nil {
  117. return false
  118. }
  119. return time.Now().Before(*t.LockedUntil)
  120. }
  121. // CreateBackupCodes 创建备用码
  122. func CreateBackupCodes(userId int, codes []string) error {
  123. return DB.Transaction(func(tx *gorm.DB) error {
  124. // 先删除现有的备用码
  125. if err := tx.Where("user_id = ?", userId).Delete(&TwoFABackupCode{}).Error; err != nil {
  126. return err
  127. }
  128. // 创建新的备用码记录
  129. for _, code := range codes {
  130. hashedCode, err := common.HashBackupCode(code)
  131. if err != nil {
  132. return err
  133. }
  134. backupCode := TwoFABackupCode{
  135. UserId: userId,
  136. CodeHash: hashedCode,
  137. IsUsed: false,
  138. }
  139. if err := tx.Create(&backupCode).Error; err != nil {
  140. return err
  141. }
  142. }
  143. return nil
  144. })
  145. }
  146. // ValidateBackupCode 验证并使用备用码
  147. func ValidateBackupCode(userId int, code string) (bool, error) {
  148. if !common.ValidateBackupCode(code) {
  149. return false, errors.New("验证码或备用码不正确")
  150. }
  151. normalizedCode := common.NormalizeBackupCode(code)
  152. // 查找未使用的备用码
  153. var backupCodes []TwoFABackupCode
  154. if err := DB.Where("user_id = ? AND is_used = false", userId).Find(&backupCodes).Error; err != nil {
  155. return false, err
  156. }
  157. // 验证备用码
  158. for _, bc := range backupCodes {
  159. if common.ValidatePasswordAndHash(normalizedCode, bc.CodeHash) {
  160. // 标记为已使用
  161. now := time.Now()
  162. bc.IsUsed = true
  163. bc.UsedAt = &now
  164. if err := DB.Save(&bc).Error; err != nil {
  165. return false, err
  166. }
  167. return true, nil
  168. }
  169. }
  170. return false, nil
  171. }
  172. // GetUnusedBackupCodeCount 获取未使用的备用码数量
  173. func GetUnusedBackupCodeCount(userId int) (int, error) {
  174. var count int64
  175. err := DB.Model(&TwoFABackupCode{}).Where("user_id = ? AND is_used = false", userId).Count(&count).Error
  176. return int(count), err
  177. }
  178. // DisableTwoFA 禁用用户的2FA
  179. func DisableTwoFA(userId int) error {
  180. twoFA, err := GetTwoFAByUserId(userId)
  181. if err != nil {
  182. return err
  183. }
  184. if twoFA == nil {
  185. return ErrTwoFANotEnabled
  186. }
  187. // 删除2FA设置和备用码
  188. return twoFA.Delete()
  189. }
  190. // EnableTwoFA 启用2FA
  191. func (t *TwoFA) Enable() error {
  192. t.IsEnabled = true
  193. t.FailedAttempts = 0
  194. t.LockedUntil = nil
  195. return t.Update()
  196. }
  197. // ValidateTOTPAndUpdateUsage 验证TOTP并更新使用记录
  198. func (t *TwoFA) ValidateTOTPAndUpdateUsage(code string) (bool, error) {
  199. // 检查是否被锁定
  200. if t.IsLocked() {
  201. return false, fmt.Errorf("账户已被锁定,请在%v后重试", t.LockedUntil.Format("2006-01-02 15:04:05"))
  202. }
  203. // 验证TOTP码
  204. if !common.ValidateTOTPCode(t.Secret, code) {
  205. // 增加失败次数
  206. if err := t.IncrementFailedAttempts(); err != nil {
  207. common.SysLog("更新2FA失败次数失败: " + err.Error())
  208. }
  209. return false, nil
  210. }
  211. // 验证成功,重置失败次数并更新最后使用时间
  212. now := time.Now()
  213. t.FailedAttempts = 0
  214. t.LockedUntil = nil
  215. t.LastUsedAt = &now
  216. if err := t.Update(); err != nil {
  217. common.SysLog("更新2FA使用记录失败: " + err.Error())
  218. }
  219. return true, nil
  220. }
  221. // ValidateBackupCodeAndUpdateUsage 验证备用码并更新使用记录
  222. func (t *TwoFA) ValidateBackupCodeAndUpdateUsage(code string) (bool, error) {
  223. // 检查是否被锁定
  224. if t.IsLocked() {
  225. return false, fmt.Errorf("账户已被锁定,请在%v后重试", t.LockedUntil.Format("2006-01-02 15:04:05"))
  226. }
  227. // 验证备用码
  228. valid, err := ValidateBackupCode(t.UserId, code)
  229. if err != nil {
  230. return false, err
  231. }
  232. if !valid {
  233. // 增加失败次数
  234. if err := t.IncrementFailedAttempts(); err != nil {
  235. common.SysLog("更新2FA失败次数失败: " + err.Error())
  236. }
  237. return false, nil
  238. }
  239. // 验证成功,重置失败次数并更新最后使用时间
  240. now := time.Now()
  241. t.FailedAttempts = 0
  242. t.LockedUntil = nil
  243. t.LastUsedAt = &now
  244. if err := t.Update(); err != nil {
  245. common.SysLog("更新2FA使用记录失败: " + err.Error())
  246. }
  247. return true, nil
  248. }
  249. // GetTwoFAStats 获取2FA统计信息(管理员使用)
  250. func GetTwoFAStats() (map[string]interface{}, error) {
  251. var totalUsers, enabledUsers int64
  252. // 总用户数
  253. if err := DB.Model(&User{}).Count(&totalUsers).Error; err != nil {
  254. return nil, err
  255. }
  256. // 启用2FA的用户数
  257. if err := DB.Model(&TwoFA{}).Where("is_enabled = true").Count(&enabledUsers).Error; err != nil {
  258. return nil, err
  259. }
  260. enabledRate := float64(0)
  261. if totalUsers > 0 {
  262. enabledRate = float64(enabledUsers) / float64(totalUsers) * 100
  263. }
  264. return map[string]interface{}{
  265. "total_users": totalUsers,
  266. "enabled_users": enabledUsers,
  267. "enabled_rate": fmt.Sprintf("%.1f%%", enabledRate),
  268. }, nil
  269. }