feat(device/session): per-user max devices & TTL, WebDAV reactivation, admin clean/list APIs (#9315)
* feat(auth): Improved device session management logic - Replaced the `userID` parameter with the `user` object to support operations with more user attributes. - Introduced `SessionTTL` and `MaxDevices` properties in the `Handle` and `EnsureActiveOnLogin` functions to support user-defined settings. - Adjusted the session creation and verification logic in `session.go` to support user-defined device count and session duration. - Added help documentation in `setting.go` to explain the configuration purposes of `MaxDevices` and `DeviceSessionTTL`. - Added optional `MaxDevices` and `SessionTTL` properties to the user entity in `user.go` and persisted these settings across user updates. - Modified the device handling logic in `webdav.go` to adapt to the new user object parameters. * feat(session): Added session cleanup functionality - Added the `/clean` route to the route for session cleanup - Added the `DeleteInactiveSessions` method to support deleting inactive sessions by user ID - Added the `DeleteSessionByID` method to delete a specific session by session ID - Defined the `CleanSessionsReq` request structure to support passing a user ID or session ID - Implemented the `CleanSessions` interface logic to perform corresponding session cleanup operations based on the request parameters * feat(session): Added session list functionality with usernames - Added the `SessionWithUser` structure, which includes `Session` and `Username` fields. - Added the `ListSessionsWithUser` function, which queries and returns a list of sessions with usernames. - Used a `JOIN` operation to join the session and user tables to retrieve the username associated with each session. - Changed `ListSessions` to `ListSessionsWithUser` to ensure that the username is retrieved. * feat(webdav): Enhanced WebDAV authentication logic - Added logic for generating device keys based on the Client-Id, prioritizing those obtained from the request header. - If the Client-Id is missing, attempts to obtain it from the cookie. If that still doesn't exist, generates a random suffix for the client IP address as an identifier. - Stores the generated Client-Id in a cookie to ensure consistency across subsequent requests. - Use the device.EnsureActiveOnLogin method instead of the original Handle method to reactivate inactive sessions.feat/limit-webdav
parent
fcbc79cb24
commit
eb4c35db75
|
|
@ -165,9 +165,9 @@ func InitialSettings() []model.SettingItem {
|
||||||
{Key: conf.ForwardDirectLinkParams, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL},
|
{Key: conf.ForwardDirectLinkParams, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL},
|
||||||
{Key: conf.IgnoreDirectLinkParams, Value: "sign,alist_ts", Type: conf.TypeString, Group: model.GLOBAL},
|
{Key: conf.IgnoreDirectLinkParams, Value: "sign,alist_ts", Type: conf.TypeString, Group: model.GLOBAL},
|
||||||
{Key: conf.WebauthnLoginEnabled, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL, Flag: model.PUBLIC},
|
{Key: conf.WebauthnLoginEnabled, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL, Flag: model.PUBLIC},
|
||||||
{Key: conf.MaxDevices, Value: "0", Type: conf.TypeNumber, Group: model.GLOBAL},
|
{Key: conf.MaxDevices, Value: "0", Type: conf.TypeNumber, Group: model.GLOBAL, Help: `max devices per user (0 for unlimited)`},
|
||||||
{Key: conf.DeviceEvictPolicy, Value: "deny", Type: conf.TypeSelect, Options: "deny,evict_oldest", Group: model.GLOBAL},
|
{Key: conf.DeviceEvictPolicy, Value: "deny", Type: conf.TypeSelect, Options: "deny,evict_oldest", Group: model.GLOBAL},
|
||||||
{Key: conf.DeviceSessionTTL, Value: "86400", Type: conf.TypeNumber, Group: model.GLOBAL},
|
{Key: conf.DeviceSessionTTL, Value: "86400", Type: conf.TypeNumber, Group: model.GLOBAL, Help: `session ttl in seconds (0 disables)`},
|
||||||
|
|
||||||
// single settings
|
// single settings
|
||||||
{Key: conf.Token, Value: token, Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE},
|
{Key: conf.Token, Value: token, Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE},
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,17 @@
|
||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type SessionWithUser struct {
|
||||||
|
model.Session
|
||||||
|
Username string
|
||||||
|
}
|
||||||
|
|
||||||
func GetSession(userID uint, deviceKey string) (*model.Session, error) {
|
func GetSession(userID uint, deviceKey string) (*model.Session, error) {
|
||||||
s := model.Session{UserID: userID, DeviceKey: deviceKey}
|
s := model.Session{UserID: userID, DeviceKey: deviceKey}
|
||||||
if err := db.Select("user_id, device_key, last_active, status, user_agent, ip").Where(&s).First(&s).Error; err != nil {
|
if err := db.Select("user_id, device_key, last_active, status, user_agent, ip").Where(&s).First(&s).Error; err != nil {
|
||||||
|
|
@ -64,6 +70,31 @@ func ListSessions() ([]model.Session, error) {
|
||||||
return sessions, errors.WithStack(err)
|
return sessions, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ListSessionsWithUser() ([]SessionWithUser, error) {
|
||||||
|
var sessions []SessionWithUser
|
||||||
|
sessionTable := conf.Conf.Database.TablePrefix + "sessions"
|
||||||
|
userTable := conf.Conf.Database.TablePrefix + "users"
|
||||||
|
err := db.Table(sessionTable).
|
||||||
|
Select(sessionTable+".user_id, "+sessionTable+".device_key, "+sessionTable+".last_active, "+
|
||||||
|
sessionTable+".status, "+sessionTable+".user_agent, "+sessionTable+".ip, "+userTable+".username").
|
||||||
|
Joins("JOIN "+userTable+" ON "+sessionTable+".user_id = "+userTable+".id").
|
||||||
|
Where(sessionTable+".status = ?", model.SessionActive).
|
||||||
|
Scan(&sessions).Error
|
||||||
|
return sessions, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
func MarkInactive(sessionID string) error {
|
func MarkInactive(sessionID string) error {
|
||||||
return errors.WithStack(db.Model(&model.Session{}).Where("device_key = ?", sessionID).Update("status", model.SessionInactive).Error)
|
return errors.WithStack(db.Model(&model.Session{}).Where("device_key = ?", sessionID).Update("status", model.SessionInactive).Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DeleteInactiveSessions(userID *uint) error {
|
||||||
|
query := db.Where("status = ?", model.SessionInactive)
|
||||||
|
if userID != nil {
|
||||||
|
query = query.Where("user_id = ?", *userID)
|
||||||
|
}
|
||||||
|
return errors.WithStack(query.Delete(&model.Session{}).Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteSessionByID(sessionID string) error {
|
||||||
|
return errors.WithStack(db.Where("device_key = ?", sessionID).Delete(&model.Session{}).Error)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handle verifies device sessions for a user and upserts current session.
|
// Handle verifies device sessions for a user and upserts current session.
|
||||||
func Handle(userID uint, deviceKey, ua, ip string) error {
|
func Handle(user *model.User, deviceKey, ua, ip string) error {
|
||||||
ttl := setting.GetInt(conf.DeviceSessionTTL, 86400)
|
ttl := setting.GetInt(conf.DeviceSessionTTL, 86400)
|
||||||
|
if user.SessionTTL != nil {
|
||||||
|
ttl = *user.SessionTTL
|
||||||
|
}
|
||||||
if ttl > 0 {
|
if ttl > 0 {
|
||||||
_ = db.DeleteSessionsBefore(time.Now().Unix() - int64(ttl))
|
_ = db.DeleteSessionsBefore(time.Now().Unix() - int64(ttl))
|
||||||
}
|
}
|
||||||
|
|
@ -23,7 +26,7 @@ func Handle(userID uint, deviceKey, ua, ip string) error {
|
||||||
ip = utils.MaskIP(ip)
|
ip = utils.MaskIP(ip)
|
||||||
|
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
sess, err := db.GetSession(userID, deviceKey)
|
sess, err := db.GetSession(user.ID, deviceKey)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if sess.Status == model.SessionInactive {
|
if sess.Status == model.SessionInactive {
|
||||||
return errors.WithStack(errs.SessionInactive)
|
return errors.WithStack(errs.SessionInactive)
|
||||||
|
|
@ -39,15 +42,18 @@ func Handle(userID uint, deviceKey, ua, ip string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
max := setting.GetInt(conf.MaxDevices, 0)
|
max := setting.GetInt(conf.MaxDevices, 0)
|
||||||
|
if user.MaxDevices != nil {
|
||||||
|
max = *user.MaxDevices
|
||||||
|
}
|
||||||
if max > 0 {
|
if max > 0 {
|
||||||
count, err := db.CountActiveSessionsByUser(userID)
|
count, err := db.CountActiveSessionsByUser(user.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if count >= int64(max) {
|
if count >= int64(max) {
|
||||||
policy := setting.GetStr(conf.DeviceEvictPolicy, "deny")
|
policy := setting.GetStr(conf.DeviceEvictPolicy, "deny")
|
||||||
if policy == "evict_oldest" {
|
if policy == "evict_oldest" {
|
||||||
if oldest, err := db.GetOldestActiveSession(userID); err == nil {
|
if oldest, err := db.GetOldestActiveSession(user.ID); err == nil {
|
||||||
if err := db.MarkInactive(oldest.DeviceKey); err != nil {
|
if err := db.MarkInactive(oldest.DeviceKey); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -58,30 +64,40 @@ func Handle(userID uint, deviceKey, ua, ip string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s := &model.Session{UserID: userID, DeviceKey: deviceKey, UserAgent: ua, IP: ip, LastActive: now, Status: model.SessionActive}
|
s := &model.Session{UserID: user.ID, DeviceKey: deviceKey, UserAgent: ua, IP: ip, LastActive: now, Status: model.SessionActive}
|
||||||
return db.CreateSession(s)
|
return db.CreateSession(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnsureActiveOnLogin is used only in login flow:
|
// EnsureActiveOnLogin is used only in login flow:
|
||||||
// - If session exists (even Inactive): reactivate and refresh fields.
|
// - If session exists (even Inactive): reactivate and refresh fields.
|
||||||
// - If not exists: apply max-devices policy, then create Active session.
|
// - If not exists: apply max-devices policy, then create Active session.
|
||||||
func EnsureActiveOnLogin(userID uint, deviceKey, ua, ip string) error {
|
func EnsureActiveOnLogin(user *model.User, deviceKey, ua, ip string) error {
|
||||||
|
ttl := setting.GetInt(conf.DeviceSessionTTL, 86400)
|
||||||
|
if user.SessionTTL != nil {
|
||||||
|
ttl = *user.SessionTTL
|
||||||
|
}
|
||||||
|
if ttl > 0 {
|
||||||
|
_ = db.DeleteSessionsBefore(time.Now().Unix() - int64(ttl))
|
||||||
|
}
|
||||||
ip = utils.MaskIP(ip)
|
ip = utils.MaskIP(ip)
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
|
|
||||||
sess, err := db.GetSession(userID, deviceKey)
|
sess, err := db.GetSession(user.ID, deviceKey)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if sess.Status == model.SessionInactive {
|
if sess.Status == model.SessionInactive {
|
||||||
max := setting.GetInt(conf.MaxDevices, 0)
|
max := setting.GetInt(conf.MaxDevices, 0)
|
||||||
|
if user.MaxDevices != nil {
|
||||||
|
max = *user.MaxDevices
|
||||||
|
}
|
||||||
if max > 0 {
|
if max > 0 {
|
||||||
count, err := db.CountActiveSessionsByUser(userID)
|
count, err := db.CountActiveSessionsByUser(user.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if count >= int64(max) {
|
if count >= int64(max) {
|
||||||
policy := setting.GetStr(conf.DeviceEvictPolicy, "deny")
|
policy := setting.GetStr(conf.DeviceEvictPolicy, "deny")
|
||||||
if policy == "evict_oldest" {
|
if policy == "evict_oldest" {
|
||||||
if oldest, gerr := db.GetOldestActiveSession(userID); gerr == nil {
|
if oldest, gerr := db.GetOldestActiveSession(user.ID); gerr == nil {
|
||||||
if err := db.MarkInactive(oldest.DeviceKey); err != nil {
|
if err := db.MarkInactive(oldest.DeviceKey); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -103,15 +119,18 @@ func EnsureActiveOnLogin(userID uint, deviceKey, ua, ip string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
max := setting.GetInt(conf.MaxDevices, 0)
|
max := setting.GetInt(conf.MaxDevices, 0)
|
||||||
|
if user.MaxDevices != nil {
|
||||||
|
max = *user.MaxDevices
|
||||||
|
}
|
||||||
if max > 0 {
|
if max > 0 {
|
||||||
count, err := db.CountActiveSessionsByUser(userID)
|
count, err := db.CountActiveSessionsByUser(user.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if count >= int64(max) {
|
if count >= int64(max) {
|
||||||
policy := setting.GetStr(conf.DeviceEvictPolicy, "deny")
|
policy := setting.GetStr(conf.DeviceEvictPolicy, "deny")
|
||||||
if policy == "evict_oldest" {
|
if policy == "evict_oldest" {
|
||||||
if oldest, gerr := db.GetOldestActiveSession(userID); gerr == nil {
|
if oldest, gerr := db.GetOldestActiveSession(user.ID); gerr == nil {
|
||||||
if err := db.MarkInactive(oldest.DeviceKey); err != nil {
|
if err := db.MarkInactive(oldest.DeviceKey); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -123,7 +142,7 @@ func EnsureActiveOnLogin(userID uint, deviceKey, ua, ip string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
return db.CreateSession(&model.Session{
|
return db.CreateSession(&model.Session{
|
||||||
UserID: userID,
|
UserID: user.ID,
|
||||||
DeviceKey: deviceKey,
|
DeviceKey: deviceKey,
|
||||||
UserAgent: ua,
|
UserAgent: ua,
|
||||||
IP: ip,
|
IP: ip,
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,8 @@ type User struct {
|
||||||
Permission int32 `json:"permission"`
|
Permission int32 `json:"permission"`
|
||||||
OtpSecret string `json:"-"`
|
OtpSecret string `json:"-"`
|
||||||
SsoID string `json:"sso_id"` // unique by sso platform
|
SsoID string `json:"sso_id"` // unique by sso platform
|
||||||
|
MaxDevices *int `json:"max_devices"`
|
||||||
|
SessionTTL *int `json:"session_ttl"`
|
||||||
Authn string `gorm:"type:text" json:"-"`
|
Authn string `gorm:"type:text" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ func loginHash(c *gin.Context, req *LoginReq) {
|
||||||
key := utils.GetMD5EncodeStr(fmt.Sprintf("%d-%s",
|
key := utils.GetMD5EncodeStr(fmt.Sprintf("%d-%s",
|
||||||
user.ID, clientID))
|
user.ID, clientID))
|
||||||
|
|
||||||
if err := device.EnsureActiveOnLogin(user.ID, key, c.Request.UserAgent(), c.ClientIP()); err != nil {
|
if err := device.EnsureActiveOnLogin(user, key, c.Request.UserAgent(), c.ClientIP()); err != nil {
|
||||||
if errors.Is(err, errs.TooManyDevices) {
|
if errors.Is(err, errs.TooManyDevices) {
|
||||||
common.ErrorResp(c, err, 403)
|
common.ErrorResp(c, err, 403)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
type SessionResp struct {
|
type SessionResp struct {
|
||||||
SessionID string `json:"session_id"`
|
SessionID string `json:"session_id"`
|
||||||
UserID uint `json:"user_id,omitempty"`
|
UserID uint `json:"user_id,omitempty"`
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
LastActive int64 `json:"last_active"`
|
LastActive int64 `json:"last_active"`
|
||||||
Status int `json:"status"`
|
Status int `json:"status"`
|
||||||
UA string `json:"ua"`
|
UA string `json:"ua"`
|
||||||
|
|
@ -40,6 +41,11 @@ type EvictSessionReq struct {
|
||||||
SessionID string `json:"session_id"`
|
SessionID string `json:"session_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CleanSessionsReq struct {
|
||||||
|
UserID *uint `json:"user_id"`
|
||||||
|
SessionID string `json:"session_id"`
|
||||||
|
}
|
||||||
|
|
||||||
func EvictMySession(c *gin.Context) {
|
func EvictMySession(c *gin.Context) {
|
||||||
var req EvictSessionReq
|
var req EvictSessionReq
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
|
@ -59,7 +65,7 @@ func EvictMySession(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListSessions(c *gin.Context) {
|
func ListSessions(c *gin.Context) {
|
||||||
sessions, err := db.ListSessions()
|
sessions, err := db.ListSessionsWithUser()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorResp(c, err, 500)
|
||||||
return
|
return
|
||||||
|
|
@ -69,6 +75,7 @@ func ListSessions(c *gin.Context) {
|
||||||
resp[i] = SessionResp{
|
resp[i] = SessionResp{
|
||||||
SessionID: s.DeviceKey,
|
SessionID: s.DeviceKey,
|
||||||
UserID: s.UserID,
|
UserID: s.UserID,
|
||||||
|
Username: s.Username,
|
||||||
LastActive: s.LastActive,
|
LastActive: s.LastActive,
|
||||||
Status: s.Status,
|
Status: s.Status,
|
||||||
UA: s.UserAgent,
|
UA: s.UserAgent,
|
||||||
|
|
@ -90,3 +97,32 @@ func EvictSession(c *gin.Context) {
|
||||||
}
|
}
|
||||||
common.SuccessResp(c)
|
common.SuccessResp(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CleanSessions(c *gin.Context) {
|
||||||
|
var req CleanSessionsReq
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.SessionID != "" {
|
||||||
|
if err := db.DeleteSessionByID(req.SessionID); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.UserID != nil {
|
||||||
|
if err := db.DeleteInactiveSessions(req.UserID); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := db.DeleteInactiveSessions(nil); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,12 @@ func UpdateUser(c *gin.Context) {
|
||||||
if req.OtpSecret == "" {
|
if req.OtpSecret == "" {
|
||||||
req.OtpSecret = user.OtpSecret
|
req.OtpSecret = user.OtpSecret
|
||||||
}
|
}
|
||||||
|
if req.MaxDevices == nil {
|
||||||
|
req.MaxDevices = user.MaxDevices
|
||||||
|
}
|
||||||
|
if req.SessionTTL == nil {
|
||||||
|
req.SessionTTL = user.SessionTTL
|
||||||
|
}
|
||||||
if req.Disabled && user.IsAdmin() {
|
if req.Disabled && user.IsAdmin() {
|
||||||
count, err := op.CountEnabledAdminsExcluding(user.ID)
|
count, err := op.CountEnabledAdminsExcluding(user.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ func HandleSession(c *gin.Context, user *model.User) bool {
|
||||||
clientID = c.Query("client_id")
|
clientID = c.Query("client_id")
|
||||||
}
|
}
|
||||||
key := utils.GetMD5EncodeStr(fmt.Sprintf("%d-%s", user.ID, clientID))
|
key := utils.GetMD5EncodeStr(fmt.Sprintf("%d-%s", user.ID, clientID))
|
||||||
if err := device.Handle(user.ID, key, c.Request.UserAgent(), c.ClientIP()); err != nil {
|
if err := device.Handle(user, key, c.Request.UserAgent(), c.ClientIP()); err != nil {
|
||||||
token := c.GetHeader("Authorization")
|
token := c.GetHeader("Authorization")
|
||||||
if errors.Is(err, errs.SessionInactive) {
|
if errors.Is(err, errs.SessionInactive) {
|
||||||
_ = common.InvalidateToken(token)
|
_ = common.InvalidateToken(token)
|
||||||
|
|
|
||||||
|
|
@ -190,6 +190,7 @@ func admin(g *gin.RouterGroup) {
|
||||||
session := g.Group("/session")
|
session := g.Group("/session")
|
||||||
session.GET("/list", handles.ListSessions)
|
session.GET("/list", handles.ListSessions)
|
||||||
session.POST("/evict", handles.EvictSession)
|
session.POST("/evict", handles.EvictSession)
|
||||||
|
session.POST("/clean", handles.CleanSessions)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"github.com/alist-org/alist/v3/internal/op"
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
"github.com/alist-org/alist/v3/internal/setting"
|
"github.com/alist-org/alist/v3/internal/setting"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils/random"
|
||||||
"github.com/alist-org/alist/v3/server/common"
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
"github.com/alist-org/alist/v3/server/webdav"
|
"github.com/alist-org/alist/v3/server/webdav"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
@ -55,6 +56,10 @@ func ServeWebDAV(c *gin.Context) {
|
||||||
handler.ServeHTTP(c.Writer, c.Request.WithContext(ctx))
|
handler.ServeHTTP(c.Writer, c.Request.WithContext(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WebDAVAuth authenticates WebDAV requests and reactivates inactive
|
||||||
|
// sessions using device.EnsureActiveOnLogin. Device keys are based on
|
||||||
|
// Client-Id headers when available or the client IP with a random
|
||||||
|
// suffix to avoid conflicts.
|
||||||
func WebDAVAuth(c *gin.Context) {
|
func WebDAVAuth(c *gin.Context) {
|
||||||
guest, _ := op.GetGuest()
|
guest, _ := op.GetGuest()
|
||||||
username, password, ok := c.Request.BasicAuth()
|
username, password, ok := c.Request.BasicAuth()
|
||||||
|
|
@ -72,8 +77,17 @@ func WebDAVAuth(c *gin.Context) {
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
key := utils.GetMD5EncodeStr(fmt.Sprintf("%d-%s", admin.ID, c.ClientIP()))
|
clientID := c.GetHeader("Client-Id")
|
||||||
if err := device.Handle(admin.ID, key, c.Request.UserAgent(), c.ClientIP()); err != nil {
|
if clientID == "" {
|
||||||
|
if cookie, err := c.Request.Cookie("Client-Id"); err == nil {
|
||||||
|
clientID = cookie.Value
|
||||||
|
} else {
|
||||||
|
clientID = c.ClientIP() + "-" + random.String(8)
|
||||||
|
http.SetCookie(c.Writer, &http.Cookie{Name: "Client-Id", Value: clientID, Path: "/"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
key := utils.GetMD5EncodeStr(fmt.Sprintf("%d-%s", admin.ID, clientID))
|
||||||
|
if err := device.EnsureActiveOnLogin(admin, key, c.Request.UserAgent(), c.ClientIP()); err != nil {
|
||||||
c.Status(http.StatusForbidden)
|
c.Status(http.StatusForbidden)
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
|
|
@ -156,8 +170,17 @@ func WebDAVAuth(c *gin.Context) {
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
key := utils.GetMD5EncodeStr(fmt.Sprintf("%d-%s", user.ID, c.ClientIP()))
|
clientID := c.GetHeader("Client-Id")
|
||||||
if err := device.Handle(user.ID, key, c.Request.UserAgent(), c.ClientIP()); err != nil {
|
if clientID == "" {
|
||||||
|
if cookie, err := c.Request.Cookie("Client-Id"); err == nil {
|
||||||
|
clientID = cookie.Value
|
||||||
|
} else {
|
||||||
|
clientID = c.ClientIP() + "-" + random.String(8)
|
||||||
|
http.SetCookie(c.Writer, &http.Cookie{Name: "Client-Id", Value: clientID, Path: "/"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
key := utils.GetMD5EncodeStr(fmt.Sprintf("%d-%s", user.ID, clientID))
|
||||||
|
if err := device.EnsureActiveOnLogin(user, key, c.Request.UserAgent(), c.ClientIP()); err != nil {
|
||||||
c.Status(http.StatusForbidden)
|
c.Status(http.StatusForbidden)
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue