2024-01-06 17:56:32 +00:00
|
|
|
package security
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2024-02-18 07:51:27 +00:00
|
|
|
"github.com/google/uuid"
|
|
|
|
"strings"
|
2024-01-06 17:56:32 +00:00
|
|
|
"time"
|
|
|
|
|
2024-02-18 03:20:22 +00:00
|
|
|
"code.smartsheep.studio/hydrogen/identity/pkg/database"
|
|
|
|
"code.smartsheep.studio/hydrogen/identity/pkg/models"
|
2024-01-06 17:56:32 +00:00
|
|
|
"github.com/samber/lo"
|
|
|
|
"gorm.io/datatypes"
|
|
|
|
)
|
|
|
|
|
2024-01-30 07:57:49 +00:00
|
|
|
func CalcRisk(user models.Account, ip, ua string) int {
|
2024-01-06 17:56:32 +00:00
|
|
|
risk := 3
|
2024-01-30 07:57:49 +00:00
|
|
|
var secureFactor int64
|
|
|
|
if err := database.C.Where(models.AuthChallenge{
|
|
|
|
AccountID: user.ID,
|
|
|
|
IpAddress: ip,
|
|
|
|
}).Model(models.AuthChallenge{}).Count(&secureFactor).Error; err == nil {
|
|
|
|
if secureFactor >= 3 {
|
2024-01-30 13:15:15 +00:00
|
|
|
risk -= 3
|
2024-01-30 07:57:49 +00:00
|
|
|
} else if secureFactor >= 1 {
|
2024-01-30 13:15:15 +00:00
|
|
|
risk -= 2
|
2024-01-30 07:57:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return risk
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewChallenge(user models.Account, factors []models.AuthFactor, ip, ua string) (models.AuthChallenge, error) {
|
2024-01-06 17:56:32 +00:00
|
|
|
var challenge models.AuthChallenge
|
|
|
|
// Pickup any challenge if possible
|
|
|
|
if err := database.C.Where(models.AuthChallenge{
|
2024-01-30 07:57:49 +00:00
|
|
|
AccountID: user.ID,
|
2024-01-07 07:52:23 +00:00
|
|
|
}).Where("state = ?", models.ActiveChallengeState).First(&challenge).Error; err == nil {
|
2024-01-06 17:56:32 +00:00
|
|
|
return challenge, nil
|
|
|
|
}
|
|
|
|
|
2024-01-30 07:57:49 +00:00
|
|
|
// Calculate the risk level
|
|
|
|
risk := CalcRisk(user, ip, ua)
|
2024-01-06 17:56:32 +00:00
|
|
|
|
2024-01-30 07:57:49 +00:00
|
|
|
// Clamp risk in the exists requirements factor count
|
2024-01-29 10:38:19 +00:00
|
|
|
requirements := lo.Clamp(risk, 1, len(factors))
|
2024-01-06 17:56:32 +00:00
|
|
|
|
|
|
|
challenge = models.AuthChallenge{
|
|
|
|
IpAddress: ip,
|
|
|
|
UserAgent: ua,
|
|
|
|
RiskLevel: risk,
|
|
|
|
Requirements: requirements,
|
|
|
|
BlacklistFactors: datatypes.NewJSONType([]uint{}),
|
|
|
|
State: models.ActiveChallengeState,
|
|
|
|
ExpiredAt: time.Now().Add(2 * time.Hour),
|
2024-01-30 07:57:49 +00:00
|
|
|
AccountID: user.ID,
|
2024-01-06 17:56:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
err := database.C.Save(&challenge).Error
|
|
|
|
|
|
|
|
return challenge, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func DoChallenge(challenge models.AuthChallenge, factor models.AuthFactor, code string) error {
|
|
|
|
if err := challenge.IsAvailable(); err != nil {
|
2024-01-27 16:05:19 +00:00
|
|
|
challenge.State = models.ExpiredChallengeState
|
|
|
|
database.C.Save(&challenge)
|
2024-01-06 17:56:32 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
if challenge.Progress >= challenge.Requirements {
|
|
|
|
return fmt.Errorf("challenge already passed")
|
|
|
|
}
|
|
|
|
|
|
|
|
blacklist := challenge.BlacklistFactors.Data()
|
|
|
|
if lo.Contains(blacklist, factor.ID) {
|
|
|
|
return fmt.Errorf("factor in blacklist, please change another factor to challenge")
|
|
|
|
}
|
|
|
|
if err := VerifyFactor(factor, code); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
challenge.Progress++
|
|
|
|
challenge.BlacklistFactors = datatypes.NewJSONType(append(blacklist, factor.ID))
|
|
|
|
|
|
|
|
if err := database.C.Save(&challenge).Error; err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-02-18 07:51:27 +00:00
|
|
|
// Revoke some factor passwords
|
|
|
|
if factor.Type == models.EmailPasswordFactor {
|
|
|
|
factor.Secret = strings.ReplaceAll(uuid.NewString(), "-", "")
|
|
|
|
database.C.Save(&factor)
|
|
|
|
}
|
|
|
|
|
2024-01-06 17:56:32 +00:00
|
|
|
return nil
|
|
|
|
}
|