| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323 |
- package model
- import (
- "errors"
- "fmt"
- "time"
- "github.com/QuantumNous/new-api/common"
- "gorm.io/gorm"
- )
- var ErrTwoFANotEnabled = errors.New("用户未启用2FA")
- // TwoFA 用户2FA设置表
- type TwoFA struct {
- Id int `json:"id" gorm:"primaryKey"`
- UserId int `json:"user_id" gorm:"unique;not null;index"`
- Secret string `json:"-" gorm:"type:varchar(255);not null"` // TOTP密钥,不返回给前端
- IsEnabled bool `json:"is_enabled"`
- FailedAttempts int `json:"failed_attempts" gorm:"default:0"`
- LockedUntil *time.Time `json:"locked_until,omitempty"`
- LastUsedAt *time.Time `json:"last_used_at,omitempty"`
- CreatedAt time.Time `json:"created_at"`
- UpdatedAt time.Time `json:"updated_at"`
- DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
- }
- // TwoFABackupCode 备用码使用记录表
- type TwoFABackupCode struct {
- Id int `json:"id" gorm:"primaryKey"`
- UserId int `json:"user_id" gorm:"not null;index"`
- CodeHash string `json:"-" gorm:"type:varchar(255);not null"` // 备用码哈希
- IsUsed bool `json:"is_used"`
- UsedAt *time.Time `json:"used_at,omitempty"`
- CreatedAt time.Time `json:"created_at"`
- DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
- }
- // GetTwoFAByUserId 根据用户ID获取2FA设置
- func GetTwoFAByUserId(userId int) (*TwoFA, error) {
- if userId == 0 {
- return nil, errors.New("用户ID不能为空")
- }
- var twoFA TwoFA
- err := DB.Where("user_id = ?", userId).First(&twoFA).Error
- if err != nil {
- if errors.Is(err, gorm.ErrRecordNotFound) {
- return nil, nil // 返回nil表示未设置2FA
- }
- return nil, err
- }
- return &twoFA, nil
- }
- // IsTwoFAEnabled 检查用户是否启用了2FA
- func IsTwoFAEnabled(userId int) bool {
- twoFA, err := GetTwoFAByUserId(userId)
- if err != nil || twoFA == nil {
- return false
- }
- return twoFA.IsEnabled
- }
- // CreateTwoFA 创建2FA设置
- func (t *TwoFA) Create() error {
- // 检查用户是否已存在2FA设置
- existing, err := GetTwoFAByUserId(t.UserId)
- if err != nil {
- return err
- }
- if existing != nil {
- return errors.New("用户已存在2FA设置")
- }
- // 验证用户存在
- var user User
- if err := DB.First(&user, t.UserId).Error; err != nil {
- if errors.Is(err, gorm.ErrRecordNotFound) {
- return errors.New("用户不存在")
- }
- return err
- }
- return DB.Create(t).Error
- }
- // Update 更新2FA设置
- func (t *TwoFA) Update() error {
- if t.Id == 0 {
- return errors.New("2FA记录ID不能为空")
- }
- return DB.Save(t).Error
- }
- // Delete 删除2FA设置
- func (t *TwoFA) Delete() error {
- if t.Id == 0 {
- return errors.New("2FA记录ID不能为空")
- }
- // 使用事务确保原子性
- return DB.Transaction(func(tx *gorm.DB) error {
- // 同时删除相关的备用码记录(硬删除)
- if err := tx.Unscoped().Where("user_id = ?", t.UserId).Delete(&TwoFABackupCode{}).Error; err != nil {
- return err
- }
- // 硬删除2FA记录
- return tx.Unscoped().Delete(t).Error
- })
- }
- // ResetFailedAttempts 重置失败尝试次数
- func (t *TwoFA) ResetFailedAttempts() error {
- t.FailedAttempts = 0
- t.LockedUntil = nil
- return t.Update()
- }
- // IncrementFailedAttempts 增加失败尝试次数
- func (t *TwoFA) IncrementFailedAttempts() error {
- t.FailedAttempts++
- // 检查是否需要锁定
- if t.FailedAttempts >= common.MaxFailAttempts {
- lockUntil := time.Now().Add(time.Duration(common.LockoutDuration) * time.Second)
- t.LockedUntil = &lockUntil
- }
- return t.Update()
- }
- // IsLocked 检查账户是否被锁定
- func (t *TwoFA) IsLocked() bool {
- if t.LockedUntil == nil {
- return false
- }
- return time.Now().Before(*t.LockedUntil)
- }
- // CreateBackupCodes 创建备用码
- func CreateBackupCodes(userId int, codes []string) error {
- return DB.Transaction(func(tx *gorm.DB) error {
- // 先删除现有的备用码
- if err := tx.Where("user_id = ?", userId).Delete(&TwoFABackupCode{}).Error; err != nil {
- return err
- }
- // 创建新的备用码记录
- for _, code := range codes {
- hashedCode, err := common.HashBackupCode(code)
- if err != nil {
- return err
- }
- backupCode := TwoFABackupCode{
- UserId: userId,
- CodeHash: hashedCode,
- IsUsed: false,
- }
- if err := tx.Create(&backupCode).Error; err != nil {
- return err
- }
- }
- return nil
- })
- }
- // ValidateBackupCode 验证并使用备用码
- func ValidateBackupCode(userId int, code string) (bool, error) {
- if !common.ValidateBackupCode(code) {
- return false, errors.New("验证码或备用码不正确")
- }
- normalizedCode := common.NormalizeBackupCode(code)
- // 查找未使用的备用码
- var backupCodes []TwoFABackupCode
- if err := DB.Where("user_id = ? AND is_used = false", userId).Find(&backupCodes).Error; err != nil {
- return false, err
- }
- // 验证备用码
- for _, bc := range backupCodes {
- if common.ValidatePasswordAndHash(normalizedCode, bc.CodeHash) {
- // 标记为已使用
- now := time.Now()
- bc.IsUsed = true
- bc.UsedAt = &now
- if err := DB.Save(&bc).Error; err != nil {
- return false, err
- }
- return true, nil
- }
- }
- return false, nil
- }
- // GetUnusedBackupCodeCount 获取未使用的备用码数量
- func GetUnusedBackupCodeCount(userId int) (int, error) {
- var count int64
- err := DB.Model(&TwoFABackupCode{}).Where("user_id = ? AND is_used = false", userId).Count(&count).Error
- return int(count), err
- }
- // DisableTwoFA 禁用用户的2FA
- func DisableTwoFA(userId int) error {
- twoFA, err := GetTwoFAByUserId(userId)
- if err != nil {
- return err
- }
- if twoFA == nil {
- return ErrTwoFANotEnabled
- }
- // 删除2FA设置和备用码
- return twoFA.Delete()
- }
- // EnableTwoFA 启用2FA
- func (t *TwoFA) Enable() error {
- t.IsEnabled = true
- t.FailedAttempts = 0
- t.LockedUntil = nil
- return t.Update()
- }
- // ValidateTOTPAndUpdateUsage 验证TOTP并更新使用记录
- func (t *TwoFA) ValidateTOTPAndUpdateUsage(code string) (bool, error) {
- // 检查是否被锁定
- if t.IsLocked() {
- return false, fmt.Errorf("账户已被锁定,请在%v后重试", t.LockedUntil.Format("2006-01-02 15:04:05"))
- }
- // 验证TOTP码
- if !common.ValidateTOTPCode(t.Secret, code) {
- // 增加失败次数
- if err := t.IncrementFailedAttempts(); err != nil {
- common.SysLog("更新2FA失败次数失败: " + err.Error())
- }
- return false, nil
- }
- // 验证成功,重置失败次数并更新最后使用时间
- now := time.Now()
- t.FailedAttempts = 0
- t.LockedUntil = nil
- t.LastUsedAt = &now
- if err := t.Update(); err != nil {
- common.SysLog("更新2FA使用记录失败: " + err.Error())
- }
- return true, nil
- }
- // ValidateBackupCodeAndUpdateUsage 验证备用码并更新使用记录
- func (t *TwoFA) ValidateBackupCodeAndUpdateUsage(code string) (bool, error) {
- // 检查是否被锁定
- if t.IsLocked() {
- return false, fmt.Errorf("账户已被锁定,请在%v后重试", t.LockedUntil.Format("2006-01-02 15:04:05"))
- }
- // 验证备用码
- valid, err := ValidateBackupCode(t.UserId, code)
- if err != nil {
- return false, err
- }
- if !valid {
- // 增加失败次数
- if err := t.IncrementFailedAttempts(); err != nil {
- common.SysLog("更新2FA失败次数失败: " + err.Error())
- }
- return false, nil
- }
- // 验证成功,重置失败次数并更新最后使用时间
- now := time.Now()
- t.FailedAttempts = 0
- t.LockedUntil = nil
- t.LastUsedAt = &now
- if err := t.Update(); err != nil {
- common.SysLog("更新2FA使用记录失败: " + err.Error())
- }
- return true, nil
- }
- // GetTwoFAStats 获取2FA统计信息(管理员使用)
- func GetTwoFAStats() (map[string]interface{}, error) {
- var totalUsers, enabledUsers int64
- // 总用户数
- if err := DB.Model(&User{}).Count(&totalUsers).Error; err != nil {
- return nil, err
- }
- // 启用2FA的用户数
- if err := DB.Model(&TwoFA{}).Where("is_enabled = true").Count(&enabledUsers).Error; err != nil {
- return nil, err
- }
- enabledRate := float64(0)
- if totalUsers > 0 {
- enabledRate = float64(enabledUsers) / float64(totalUsers) * 100
- }
- return map[string]interface{}{
- "total_users": totalUsers,
- "enabled_users": enabledUsers,
- "enabled_rate": fmt.Sprintf("%.1f%%", enabledRate),
- }, nil
- }
|