mirror of
https://github.com/OpenListTeam/OpenList.git
synced 2025-07-19 01:48:42 +08:00
fix(security): add login count validation for webdav (#693)
This commit is contained in:
@ -9,6 +9,7 @@ import (
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/errs"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils/random"
|
||||
"github.com/OpenListTeam/go-cache"
|
||||
"github.com/go-webauthn/webauthn/webauthn"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@ -21,6 +22,13 @@ const (
|
||||
|
||||
const StaticHashSalt = "https://github.com/alist-org/alist"
|
||||
|
||||
var LoginCache = cache.NewMemCache[int]()
|
||||
|
||||
var (
|
||||
DefaultLockDuration = time.Minute * 5
|
||||
DefaultMaxAuthRetries = 5
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"` // unique key
|
||||
Username string `json:"username" gorm:"unique" binding:"required"` // username
|
||||
|
@ -4,22 +4,14 @@ import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"image/png"
|
||||
"time"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
||||
"github.com/OpenListTeam/OpenList/v4/server/common"
|
||||
"github.com/OpenListTeam/go-cache"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pquerna/otp/totp"
|
||||
)
|
||||
|
||||
var loginCache = cache.NewMemCache[int]()
|
||||
var (
|
||||
defaultDuration = time.Minute * 5
|
||||
defaultTimes = 5
|
||||
)
|
||||
|
||||
type LoginReq struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password"`
|
||||
@ -50,30 +42,30 @@ func LoginHash(c *gin.Context) {
|
||||
func loginHash(c *gin.Context, req *LoginReq) {
|
||||
// check count of login
|
||||
ip := c.ClientIP()
|
||||
count, ok := loginCache.Get(ip)
|
||||
if ok && count >= defaultTimes {
|
||||
count, ok := model.LoginCache.Get(ip)
|
||||
if ok && count >= model.DefaultMaxAuthRetries {
|
||||
common.ErrorStrResp(c, "Too many unsuccessful sign-in attempts have been made using an incorrect username or password, Try again later.", 429)
|
||||
loginCache.Expire(ip, defaultDuration)
|
||||
model.LoginCache.Expire(ip, model.DefaultLockDuration)
|
||||
return
|
||||
}
|
||||
// check username
|
||||
user, err := op.GetUserByName(req.Username)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
loginCache.Set(ip, count+1)
|
||||
model.LoginCache.Set(ip, count+1)
|
||||
return
|
||||
}
|
||||
// validate password hash
|
||||
if err := user.ValidatePwdStaticHash(req.Password); err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
loginCache.Set(ip, count+1)
|
||||
model.LoginCache.Set(ip, count+1)
|
||||
return
|
||||
}
|
||||
// check 2FA
|
||||
if user.OtpSecret != "" {
|
||||
if !totp.Validate(req.OtpCode, user.OtpSecret) {
|
||||
common.ErrorStrResp(c, "Invalid 2FA code", 402)
|
||||
loginCache.Set(ip, count+1)
|
||||
model.LoginCache.Set(ip, count+1)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -84,7 +76,7 @@ func loginHash(c *gin.Context, req *LoginReq) {
|
||||
return
|
||||
}
|
||||
common.SuccessResp(c, gin.H{"token": token})
|
||||
loginCache.Del(ip)
|
||||
model.LoginCache.Del(ip)
|
||||
}
|
||||
|
||||
type UserResp struct {
|
||||
|
@ -36,10 +36,10 @@ func loginLdap(c *gin.Context, req *LoginReq) {
|
||||
|
||||
// check count of login
|
||||
ip := c.ClientIP()
|
||||
count, ok := loginCache.Get(ip)
|
||||
if ok && count >= defaultTimes {
|
||||
count, ok := model.LoginCache.Get(ip)
|
||||
if ok && count >= model.DefaultMaxAuthRetries {
|
||||
common.ErrorStrResp(c, "Too many unsuccessful sign-in attempts have been made using an incorrect username or password, Try again later.", 429)
|
||||
loginCache.Expire(ip, defaultDuration)
|
||||
model.LoginCache.Expire(ip, model.DefaultLockDuration)
|
||||
return
|
||||
}
|
||||
|
||||
@ -94,7 +94,7 @@ func loginLdap(c *gin.Context, req *LoginReq) {
|
||||
if err != nil {
|
||||
utils.Log.Errorf("Failed to auth. %v", err)
|
||||
common.ErrorResp(c, err, 400)
|
||||
loginCache.Set(ip, count+1)
|
||||
model.LoginCache.Set(ip, count+1)
|
||||
return
|
||||
} else {
|
||||
utils.Log.Infof("Auth successful username:%s", req.Username)
|
||||
@ -106,7 +106,7 @@ func loginLdap(c *gin.Context, req *LoginReq) {
|
||||
user, err = ladpRegister(req.Username)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
loginCache.Set(ip, count+1)
|
||||
model.LoginCache.Set(ip, count+1)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -118,7 +118,7 @@ func loginLdap(c *gin.Context, req *LoginReq) {
|
||||
return
|
||||
}
|
||||
common.SuccessResp(c, gin.H{"token": token})
|
||||
loginCache.Del(ip)
|
||||
model.LoginCache.Del(ip)
|
||||
}
|
||||
|
||||
func ladpRegister(username string) (*model.User, error) {
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/stream"
|
||||
"github.com/OpenListTeam/OpenList/v4/server/middlewares"
|
||||
|
||||
@ -47,7 +48,21 @@ func ServeWebDAV(c *gin.Context) {
|
||||
}
|
||||
|
||||
func WebDAVAuth(c *gin.Context) {
|
||||
// check count of login
|
||||
ip := c.ClientIP()
|
||||
guest, _ := op.GetGuest()
|
||||
count, cok := model.LoginCache.Get(ip)
|
||||
if cok && count >= model.DefaultMaxAuthRetries {
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.Set("user", guest)
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
c.Status(http.StatusTooManyRequests)
|
||||
c.Abort()
|
||||
model.LoginCache.Expire(ip, model.DefaultLockDuration)
|
||||
return
|
||||
}
|
||||
username, password, ok := c.Request.BasicAuth()
|
||||
if !ok {
|
||||
bt := c.GetHeader("Authorization")
|
||||
@ -85,10 +100,13 @@ func WebDAVAuth(c *gin.Context) {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
model.LoginCache.Set(ip, count+1)
|
||||
c.Status(http.StatusUnauthorized)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
// at least auth is successful till here
|
||||
model.LoginCache.Del(ip)
|
||||
if user.Disabled || !user.CanWebdavRead() {
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.Set("user", guest)
|
||||
|
Reference in New Issue
Block a user