feat(session): Added user session limit and device eviction logic

- Renamed `CountSessionsByUser` to `CountActiveSessionsByUser` and added session status filtering
- Added user and device session limit, with policy handling when exceeding the limit
- Introduced device eviction policy: If the maximum number of devices is exceeded, the oldest session will be evicted using the "evict_oldest" policy
- Modified `LastActive` update logic to ensure accurate session activity time
pull/9299/head
okatu-loli 2025-08-29 11:53:55 +08:00
parent 84adba3acc
commit 8623da5361
2 changed files with 24 additions and 4 deletions

View File

@ -26,9 +26,11 @@ func DeleteSession(userID uint, deviceKey string) error {
return errors.WithStack(db.Where("user_id = ? AND device_key = ?", userID, deviceKey).Delete(&model.Session{}).Error) return errors.WithStack(db.Where("user_id = ? AND device_key = ?", userID, deviceKey).Delete(&model.Session{}).Error)
} }
func CountSessionsByUser(userID uint) (int64, error) { func CountActiveSessionsByUser(userID uint) (int64, error) {
var count int64 var count int64
err := db.Model(&model.Session{}).Where("user_id = ?", userID).Count(&count).Error err := db.Model(&model.Session{}).
Where("user_id = ? AND status = ?", userID, model.SessionActive).
Count(&count).Error
return count, errors.WithStack(err) return count, errors.WithStack(err)
} }

View File

@ -25,7 +25,25 @@ func Handle(userID uint, deviceKey, ua, ip string) error {
now := time.Now().Unix() now := time.Now().Unix()
sess, err := db.GetSession(userID, deviceKey) sess, err := db.GetSession(userID, deviceKey)
if err == nil { if err == nil {
// reactivate existing session if it was inactive if sess.Status == model.SessionInactive {
max := setting.GetInt(conf.MaxDevices, 0)
if max > 0 {
count, cerr := db.CountActiveSessionsByUser(userID)
if cerr != nil {
return cerr
}
if count >= int64(max) {
policy := setting.GetStr(conf.DeviceEvictPolicy, "deny")
if policy == "evict_oldest" {
if oldest, gerr := db.GetOldestSession(userID); gerr == nil {
_ = db.DeleteSession(userID, oldest.DeviceKey)
}
} else {
return errors.WithStack(errs.TooManyDevices)
}
}
}
}
sess.Status = model.SessionActive sess.Status = model.SessionActive
sess.LastActive = now sess.LastActive = now
sess.UserAgent = ua sess.UserAgent = ua
@ -38,7 +56,7 @@ func Handle(userID uint, deviceKey, ua, ip string) error {
max := setting.GetInt(conf.MaxDevices, 0) max := setting.GetInt(conf.MaxDevices, 0)
if max > 0 { if max > 0 {
count, err := db.CountSessionsByUser(userID) count, err := db.CountActiveSessionsByUser(userID)
if err != nil { if err != nil {
return err return err
} }