mirror of https://github.com/Xhofe/alist
feat(session): Adds session management features
- Added `SessionInactive` error type in `device.go` - Added session-related APIs in `router.go` to support listing and evicting sessions - Added `ListSessionsByUser`, `ListSessions`, and `MarkInactive` methods in `session.go` - Returns an appropriate error when the session state is `SessionInactive`pull/9286/head
parent
bdbaa85213
commit
4b1b8b2257
|
@ -47,3 +47,19 @@ func GetOldestSession(userID uint) (*model.Session, error) {
|
||||||
func UpdateSessionLastActive(userID uint, deviceKey string, lastActive int64) error {
|
func UpdateSessionLastActive(userID uint, deviceKey string, lastActive int64) error {
|
||||||
return errors.WithStack(db.Model(&model.Session{}).Where("user_id = ? AND device_key = ?", userID, deviceKey).Update("last_active", lastActive).Error)
|
return errors.WithStack(db.Model(&model.Session{}).Where("user_id = ? AND device_key = ?", userID, deviceKey).Update("last_active", lastActive).Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ListSessionsByUser(userID uint) ([]model.Session, error) {
|
||||||
|
var sessions []model.Session
|
||||||
|
err := db.Where("user_id = ? AND status = ?", userID, model.SessionActive).Find(&sessions).Error
|
||||||
|
return sessions, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListSessions() ([]model.Session, error) {
|
||||||
|
var sessions []model.Session
|
||||||
|
err := db.Where("status = ?", model.SessionActive).Find(&sessions).Error
|
||||||
|
return sessions, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MarkInactive(sessionID string) error {
|
||||||
|
return errors.WithStack(db.Model(&model.Session{}).Where("device_key = ?", sessionID).Update("status", model.SessionInactive).Error)
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,9 @@ func Handle(userID uint, deviceKey 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 {
|
||||||
|
if sess.Status == model.SessionInactive {
|
||||||
|
return errors.WithStack(errs.SessionInactive)
|
||||||
|
}
|
||||||
sess.LastActive = now
|
sess.LastActive = now
|
||||||
sess.Status = model.SessionActive
|
sess.Status = model.SessionActive
|
||||||
return db.UpsertSession(sess)
|
return db.UpsertSession(sess)
|
||||||
|
|
|
@ -4,4 +4,5 @@ import "errors"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
TooManyDevices = errors.New("too many active devices")
|
TooManyDevices = errors.New("too many active devices")
|
||||||
|
SessionInactive = errors.New("session inactive")
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
package handles
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/db"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ListMySessions(c *gin.Context) {
|
||||||
|
user := c.MustGet("user").(*model.User)
|
||||||
|
sessions, err := db.ListSessionsByUser(user.ID)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c, sessions)
|
||||||
|
}
|
||||||
|
|
||||||
|
type EvictSessionReq struct {
|
||||||
|
SessionID string `json:"session_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func EvictMySession(c *gin.Context) {
|
||||||
|
var req EvictSessionReq
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user := c.MustGet("user").(*model.User)
|
||||||
|
if _, err := db.GetSession(user.ID, req.SessionID); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := db.MarkInactive(req.SessionID); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListSessions(c *gin.Context) {
|
||||||
|
sessions, err := db.ListSessions()
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c, sessions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func EvictSession(c *gin.Context) {
|
||||||
|
var req EvictSessionReq
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := db.MarkInactive(req.SessionID); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
|
@ -71,6 +71,8 @@ func Init(e *gin.Engine) {
|
||||||
auth.POST("/auth/2fa/generate", handles.Generate2FA)
|
auth.POST("/auth/2fa/generate", handles.Generate2FA)
|
||||||
auth.POST("/auth/2fa/verify", handles.Verify2FA)
|
auth.POST("/auth/2fa/verify", handles.Verify2FA)
|
||||||
auth.GET("/auth/logout", handles.LogOut)
|
auth.GET("/auth/logout", handles.LogOut)
|
||||||
|
auth.GET("/me/sessions", handles.ListMySessions)
|
||||||
|
auth.POST("/me/sessions/evict", handles.EvictMySession)
|
||||||
|
|
||||||
// auth
|
// auth
|
||||||
api.GET("/auth/sso", handles.SSOLoginRedirect)
|
api.GET("/auth/sso", handles.SSOLoginRedirect)
|
||||||
|
@ -185,6 +187,10 @@ func admin(g *gin.RouterGroup) {
|
||||||
labelFileBinding.POST("/delete", handles.DelLabelByFileName)
|
labelFileBinding.POST("/delete", handles.DelLabelByFileName)
|
||||||
labelFileBinding.POST("/restore", handles.RestoreLabelFileBinding)
|
labelFileBinding.POST("/restore", handles.RestoreLabelFileBinding)
|
||||||
|
|
||||||
|
session := g.Group("/session")
|
||||||
|
session.GET("/list", handles.ListSessions)
|
||||||
|
session.POST("/evict", handles.EvictSession)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func _fs(g *gin.RouterGroup) {
|
func _fs(g *gin.RouterGroup) {
|
||||||
|
|
Loading…
Reference in New Issue