sessionstore.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. // Package sessionstore is a postgresql backend implementation of gorilla/sessions Session interface, based on
  2. // antonlindstrom/pgstore. Key change is to use GORM instead of typical sql driver using queries.
  3. package sessionstore
  4. import (
  5. "database/sql"
  6. "encoding/base32"
  7. "net/http"
  8. "strings"
  9. "time"
  10. "github.com/porter-dev/porter/internal/config"
  11. "github.com/gorilla/securecookie"
  12. "github.com/gorilla/sessions"
  13. "github.com/pkg/errors"
  14. "github.com/porter-dev/porter/internal/models"
  15. "github.com/porter-dev/porter/internal/repository"
  16. )
  17. // structs
  18. // PGStore is a wrapper around gorilla/sessions store.
  19. type PGStore struct {
  20. Codecs []securecookie.Codec
  21. Options *sessions.Options
  22. Path string
  23. Repo *repository.Repository
  24. }
  25. // Helpers
  26. // MaxLength restricts the maximum length of new sessions to l.
  27. // If l is 0 there is no limit to the size of a session, use with caution.
  28. // The default for a new PGStore is 4096. PostgreSQL allows for max
  29. // value sizes of up to 1GB (http://www.postgresql.org/docs/current/interactive/datatype-character.html)
  30. func (store *PGStore) MaxLength(l int) {
  31. for _, c := range store.Codecs {
  32. if codec, ok := c.(*securecookie.SecureCookie); ok {
  33. codec.MaxLength(l)
  34. }
  35. }
  36. }
  37. // MaxAge sets the maximum age for the store and the underlying cookie
  38. // implementation. Individual sessions can be deleted by setting Options.MaxAge
  39. // = -1 for that session.
  40. func (store *PGStore) MaxAge(age int) {
  41. store.Options.MaxAge = age
  42. // Set the maxAge for each securecookie instance.
  43. for _, codec := range store.Codecs {
  44. if sc, ok := codec.(*securecookie.SecureCookie); ok {
  45. sc.MaxAge(age)
  46. }
  47. }
  48. }
  49. // load fetches a session by ID from the database and decodes its content
  50. // into session.Values.
  51. func (store *PGStore) load(session *sessions.Session) error {
  52. res, err := store.Repo.Session.SelectSession(&models.Session{Key: session.ID})
  53. if err != nil {
  54. return err
  55. }
  56. return securecookie.DecodeMulti(session.Name(), string(res.Data), &session.Values, store.Codecs...)
  57. }
  58. // save writes encoded session.Values to a database record.
  59. // writes to http_sessions table by default.
  60. func (store *PGStore) save(session *sessions.Session) error {
  61. encoded, err := securecookie.EncodeMulti(session.Name(), session.Values, store.Codecs...)
  62. if err != nil {
  63. return err
  64. }
  65. exOn := session.Values["expires_on"]
  66. var expiresOn time.Time
  67. if exOn == nil {
  68. expiresOn = time.Now().Add(time.Second * time.Duration(session.Options.MaxAge))
  69. } else {
  70. expiresOn = exOn.(time.Time)
  71. if expiresOn.Sub(time.Now().Add(time.Second*time.Duration(session.Options.MaxAge))) < 0 {
  72. expiresOn = time.Now().Add(time.Second * time.Duration(session.Options.MaxAge))
  73. }
  74. }
  75. s := &models.Session{
  76. Key: session.ID,
  77. Data: []byte(encoded),
  78. ExpiresAt: expiresOn,
  79. }
  80. repo := store.Repo
  81. if session.IsNew {
  82. _, createErr := repo.Session.CreateSession(s)
  83. return createErr
  84. }
  85. _, updateErr := repo.Session.UpdateSession(s)
  86. return updateErr
  87. }
  88. // Implementation of the interface (Get, New, Save)
  89. // NewStore takes an initialized db and session key pairs to create a session-store in postgres db.
  90. func NewStore(repo *repository.Repository, conf config.ServerConf) (*PGStore, error) {
  91. keyPairs := [][]byte{
  92. conf.CookieSecret,
  93. }
  94. dbStore := &PGStore{
  95. Codecs: securecookie.CodecsFromPairs(keyPairs...),
  96. Options: &sessions.Options{
  97. Path: "/",
  98. MaxAge: 86400 * 30,
  99. },
  100. Repo: repo,
  101. }
  102. return dbStore, nil
  103. }
  104. // Get Fetches a session for a given name after it has been added to the
  105. // registry.
  106. func (store *PGStore) Get(r *http.Request, name string) (*sessions.Session, error) {
  107. return sessions.GetRegistry(r).Get(store, name)
  108. }
  109. // New returns a new session for the given name without adding it to the registry.
  110. func (store *PGStore) New(r *http.Request, name string) (*sessions.Session, error) {
  111. session := sessions.NewSession(store, name)
  112. if session == nil {
  113. return nil, nil
  114. }
  115. opts := *store.Options
  116. session.Options = &(opts)
  117. session.IsNew = true
  118. var err error
  119. if c, errCookie := r.Cookie(name); errCookie == nil {
  120. err = securecookie.DecodeMulti(name, c.Value, &session.ID, store.Codecs...)
  121. if err == nil {
  122. err = store.load(session)
  123. if err == nil {
  124. session.IsNew = false
  125. } else if errors.Cause(err) == sql.ErrNoRows {
  126. err = nil
  127. }
  128. }
  129. }
  130. store.MaxAge(store.Options.MaxAge)
  131. return session, err
  132. }
  133. // Save saves the given session into the database and deletes cookies if needed
  134. func (store *PGStore) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error {
  135. repo := store.Repo
  136. // Set delete if max-age is < 0
  137. if session.Options.MaxAge < 0 {
  138. if _, err := repo.Session.DeleteSession(&models.Session{Key: session.ID}); err != nil {
  139. return err
  140. }
  141. http.SetCookie(w, sessions.NewCookie(session.Name(), "", session.Options))
  142. return nil
  143. }
  144. if session.ID == "" {
  145. // Generate a random session ID key suitable for storage in the DB
  146. session.ID = strings.TrimRight(
  147. base32.StdEncoding.EncodeToString(
  148. securecookie.GenerateRandomKey(32),
  149. ), "=")
  150. }
  151. if err := store.save(session); err != nil {
  152. return err
  153. }
  154. // Keep the session ID key in a cookie so it can be looked up in DB later.
  155. encoded, err := securecookie.EncodeMulti(session.Name(), session.ID, store.Codecs...)
  156. if err != nil {
  157. return err
  158. }
  159. http.SetCookie(w, sessions.NewCookie(session.Name(), encoded, session.Options))
  160. return nil
  161. }