session.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. package session
  2. import (
  3. "encoding/gob"
  4. "net/http"
  5. "time"
  6. "github.com/mhsanaei/3x-ui/v3/database"
  7. "github.com/mhsanaei/3x-ui/v3/database/model"
  8. "github.com/mhsanaei/3x-ui/v3/logger"
  9. "github.com/gin-contrib/sessions"
  10. "github.com/gin-gonic/gin"
  11. )
  12. const (
  13. loginUserKey = "LOGIN_USER"
  14. loginEpochKey = "LOGIN_EPOCH"
  15. apiAuthUserKey = "api_auth_user"
  16. sessionCookieName = "3x-ui"
  17. )
  18. func init() {
  19. gob.Register(model.User{})
  20. }
  21. func SetLoginUser(c *gin.Context, user *model.User) error {
  22. if user == nil {
  23. return nil
  24. }
  25. s := sessions.Default(c)
  26. s.Set(loginUserKey, user.Id)
  27. s.Set(loginEpochKey, user.LoginEpoch)
  28. return s.Save()
  29. }
  30. func SetAPIAuthUser(c *gin.Context, user *model.User) {
  31. if user == nil {
  32. return
  33. }
  34. c.Set(apiAuthUserKey, user)
  35. }
  36. func GetLoginUser(c *gin.Context) *model.User {
  37. if v, ok := c.Get(apiAuthUserKey); ok {
  38. if u, ok2 := v.(*model.User); ok2 {
  39. return u
  40. }
  41. }
  42. s := sessions.Default(c)
  43. obj := s.Get(loginUserKey)
  44. if obj == nil {
  45. return nil
  46. }
  47. userID, ok := sessionUserID(obj)
  48. if !ok {
  49. s.Delete(loginUserKey)
  50. s.Delete(loginEpochKey)
  51. if err := s.Save(); err != nil {
  52. logger.Warning("session: failed to drop stale user payload:", err)
  53. }
  54. return nil
  55. }
  56. if legacyUserID, ok := legacySessionUserID(obj); ok {
  57. s.Set(loginUserKey, legacyUserID)
  58. if err := s.Save(); err != nil {
  59. logger.Warning("session: failed to migrate legacy user payload:", err)
  60. }
  61. }
  62. user, err := getUserByID(userID)
  63. if err != nil {
  64. logger.Warning("session: failed to load user:", err)
  65. s.Delete(loginUserKey)
  66. s.Delete(loginEpochKey)
  67. if saveErr := s.Save(); saveErr != nil {
  68. logger.Warning("session: failed to drop missing user:", saveErr)
  69. }
  70. return nil
  71. }
  72. if !sessionEpochMatches(s.Get(loginEpochKey), user.LoginEpoch) {
  73. s.Delete(loginUserKey)
  74. s.Delete(loginEpochKey)
  75. if saveErr := s.Save(); saveErr != nil {
  76. logger.Warning("session: failed to drop stale epoch:", saveErr)
  77. }
  78. return nil
  79. }
  80. return user
  81. }
  82. func sessionEpochMatches(cookieVal any, userEpoch int64) bool {
  83. var got int64
  84. switch v := cookieVal.(type) {
  85. case nil:
  86. case int64:
  87. got = v
  88. case int:
  89. got = int64(v)
  90. case int32:
  91. got = int64(v)
  92. case float64:
  93. got = int64(v)
  94. default:
  95. return false
  96. }
  97. return got == userEpoch
  98. }
  99. func IsLogin(c *gin.Context) bool {
  100. return GetLoginUser(c) != nil
  101. }
  102. func sessionUserID(obj any) (int, bool) {
  103. switch v := obj.(type) {
  104. case int:
  105. return v, v > 0
  106. case int64:
  107. return int(v), v > 0
  108. case int32:
  109. return int(v), v > 0
  110. case float64:
  111. id := int(v)
  112. return id, v == float64(id) && id > 0
  113. case model.User:
  114. return v.Id, v.Id > 0
  115. case *model.User:
  116. if v == nil {
  117. return 0, false
  118. }
  119. return v.Id, v.Id > 0
  120. default:
  121. return 0, false
  122. }
  123. }
  124. func legacySessionUserID(obj any) (int, bool) {
  125. switch v := obj.(type) {
  126. case model.User:
  127. return v.Id, v.Id > 0
  128. case *model.User:
  129. if v == nil {
  130. return 0, false
  131. }
  132. return v.Id, v.Id > 0
  133. default:
  134. return 0, false
  135. }
  136. }
  137. func getUserByID(id int) (*model.User, error) {
  138. db := database.GetDB()
  139. if db == nil {
  140. return nil, http.ErrServerClosed
  141. }
  142. user := &model.User{}
  143. if err := db.Model(model.User{}).Where("id = ?", id).First(user).Error; err != nil {
  144. return nil, err
  145. }
  146. return user, nil
  147. }
  148. func ClearSession(c *gin.Context) error {
  149. s := sessions.Default(c)
  150. s.Clear()
  151. cookiePath := c.GetString("base_path")
  152. if cookiePath == "" {
  153. cookiePath = "/"
  154. }
  155. secure := c.Request.TLS != nil
  156. s.Options(sessions.Options{
  157. Path: cookiePath,
  158. MaxAge: -1,
  159. HttpOnly: true,
  160. Secure: secure,
  161. SameSite: http.SameSiteLaxMode,
  162. })
  163. if err := s.Save(); err != nil {
  164. return err
  165. }
  166. if cookiePath != "/" {
  167. http.SetCookie(c.Writer, &http.Cookie{
  168. Name: sessionCookieName,
  169. Value: "",
  170. Path: "/",
  171. MaxAge: -1,
  172. Expires: time.Unix(0, 0),
  173. HttpOnly: true,
  174. Secure: secure,
  175. SameSite: http.SameSiteLaxMode,
  176. })
  177. }
  178. return nil
  179. }