1
0

session.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. // Package session provides session management utilities for the 3x-ui web panel.
  2. // It handles user authentication state, login sessions, and session storage using Gin sessions.
  3. package session
  4. import (
  5. "encoding/gob"
  6. "net/http"
  7. "github.com/mhsanaei/3x-ui/v2/database/model"
  8. "github.com/mhsanaei/3x-ui/v2/logger"
  9. "github.com/gin-contrib/sessions"
  10. "github.com/gin-gonic/gin"
  11. )
  12. const (
  13. loginUserKey = "LOGIN_USER"
  14. // apiAuthUserKey is the gin-context key under which checkAPIAuth
  15. // stashes a fallback user for Bearer-token-authenticated callers.
  16. // Bearer requests don't carry a session cookie, so handlers that
  17. // scope writes by user.Id (e.g. InboundController.addInbound) would
  18. // otherwise nil-deref. Keeping the override in the gin context
  19. // (not the cookie session) means the fallback never leaks into a
  20. // browser request.
  21. apiAuthUserKey = "api_auth_user"
  22. )
  23. func init() {
  24. gob.Register(model.User{})
  25. }
  26. // SetLoginUser stores the authenticated user in the session and persists it.
  27. // gin-contrib/sessions does not auto-save; callers that forget Save() leave
  28. // the cookie out of sync with server state — this helper avoids that pitfall.
  29. func SetLoginUser(c *gin.Context, user *model.User) error {
  30. if user == nil {
  31. return nil
  32. }
  33. s := sessions.Default(c)
  34. s.Set(loginUserKey, *user)
  35. return s.Save()
  36. }
  37. // SetAPIAuthUser stashes a fallback user on the gin context for the
  38. // lifetime of a single bearer-authed request. checkAPIAuth calls this
  39. // after a successful token match so downstream handlers that read
  40. // GetLoginUser don't see nil.
  41. func SetAPIAuthUser(c *gin.Context, user *model.User) {
  42. if user == nil {
  43. return
  44. }
  45. c.Set(apiAuthUserKey, user)
  46. }
  47. // GetLoginUser retrieves the authenticated user from the session.
  48. // Returns nil if no user is logged in or if the session data is invalid.
  49. func GetLoginUser(c *gin.Context) *model.User {
  50. if v, ok := c.Get(apiAuthUserKey); ok {
  51. if u, ok2 := v.(*model.User); ok2 {
  52. return u
  53. }
  54. }
  55. s := sessions.Default(c)
  56. obj := s.Get(loginUserKey)
  57. if obj == nil {
  58. return nil
  59. }
  60. user, ok := obj.(model.User)
  61. if !ok {
  62. // Stale or incompatible session payload — wipe and persist immediately
  63. // so subsequent requests don't keep hitting the same broken cookie.
  64. s.Delete(loginUserKey)
  65. if err := s.Save(); err != nil {
  66. logger.Warning("session: failed to drop stale user payload:", err)
  67. }
  68. return nil
  69. }
  70. return &user
  71. }
  72. // IsLogin checks if a user is currently authenticated in the session.
  73. func IsLogin(c *gin.Context) bool {
  74. return GetLoginUser(c) != nil
  75. }
  76. // ClearSession invalidates the session and tells the browser to drop the cookie.
  77. // The cookie attributes (Path/HttpOnly/SameSite) must mirror those used when
  78. // the cookie was created or browsers will keep it.
  79. func ClearSession(c *gin.Context) error {
  80. s := sessions.Default(c)
  81. s.Clear()
  82. cookiePath := c.GetString("base_path")
  83. if cookiePath == "" {
  84. cookiePath = "/"
  85. }
  86. s.Options(sessions.Options{
  87. Path: cookiePath,
  88. MaxAge: -1,
  89. HttpOnly: true,
  90. Secure: c.Request.TLS != nil,
  91. SameSite: http.SameSiteLaxMode,
  92. })
  93. return s.Save()
  94. }