2014-06-14 20:15:10 +04:00
|
|
|
package store
|
2014-06-13 22:36:36 +04:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/base32"
|
|
|
|
"encoding/gob"
|
|
|
|
"strings"
|
|
|
|
|
2017-04-20 11:40:57 +03:00
|
|
|
"github.com/gogo/protobuf/proto"
|
|
|
|
|
2016-02-18 12:08:51 +03:00
|
|
|
"github.com/admpub/boltstore/shared"
|
2017-07-22 07:18:03 +03:00
|
|
|
"github.com/admpub/securecookie"
|
2016-02-18 12:08:51 +03:00
|
|
|
"github.com/admpub/sessions"
|
2014-06-13 22:36:36 +04:00
|
|
|
"github.com/boltdb/bolt"
|
2016-02-18 12:18:50 +03:00
|
|
|
"github.com/webx-top/echo"
|
2014-06-13 22:36:36 +04:00
|
|
|
)
|
|
|
|
|
2014-06-14 20:29:02 +04:00
|
|
|
// Store represents a session store.
|
|
|
|
type Store struct {
|
2014-06-13 22:36:36 +04:00
|
|
|
codecs []securecookie.Codec
|
|
|
|
config Config
|
|
|
|
db *bolt.DB
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get returns a session for the given name after adding it to the registry.
|
|
|
|
//
|
|
|
|
// See gorilla/sessions FilesystemStore.Get().
|
2016-02-18 12:18:50 +03:00
|
|
|
func (s *Store) Get(ctx echo.Context, name string) (*sessions.Session, error) {
|
|
|
|
return sessions.GetRegistry(ctx).Get(s, name)
|
2014-06-13 22:36:36 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
// New returns a session for the given name without adding it to the registry.
|
|
|
|
//
|
|
|
|
// See gorilla/sessions FilesystemStore.New().
|
2017-04-20 11:40:57 +03:00
|
|
|
func (s *Store) New(ctx echo.Context, name string) (*sessions.Session, error) {
|
2014-06-13 22:36:36 +04:00
|
|
|
var err error
|
|
|
|
session := sessions.NewSession(s, name)
|
|
|
|
session.IsNew = true
|
2017-04-20 11:40:57 +03:00
|
|
|
if v := ctx.GetCookie(name); len(v) > 0 {
|
2017-07-22 07:18:03 +03:00
|
|
|
err = securecookie.DecodeMultiWithMaxAge(name, v, &session.ID, ctx.CookieOptions().MaxAge, s.codecs...)
|
2014-06-13 22:36:36 +04:00
|
|
|
if err == nil {
|
|
|
|
ok, err := s.load(session)
|
|
|
|
session.IsNew = !(err == nil && ok) // not new if no error and data available
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return session, err
|
|
|
|
}
|
|
|
|
|
2020-11-22 12:34:23 +03:00
|
|
|
func (s *Store) Reload(ctx echo.Context, session *sessions.Session) error {
|
|
|
|
ok, err := s.load(session)
|
|
|
|
session.IsNew = !(err == nil && ok) // not new if no error and data available
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2014-06-13 22:36:36 +04:00
|
|
|
// Save adds a single session to the response.
|
2017-04-20 11:40:57 +03:00
|
|
|
func (s *Store) Save(ctx echo.Context, session *sessions.Session) error {
|
2017-07-22 07:18:03 +03:00
|
|
|
if ctx.CookieOptions().MaxAge < 0 {
|
2021-05-19 06:39:18 +03:00
|
|
|
s.Remove(session.ID)
|
2017-07-22 07:18:03 +03:00
|
|
|
sessions.SetCookie(ctx, session.Name(), "")
|
2014-06-13 22:36:36 +04:00
|
|
|
} else {
|
|
|
|
// Build an alphanumeric ID.
|
2017-04-20 11:40:57 +03:00
|
|
|
if len(session.ID) == 0 {
|
2014-06-13 22:36:36 +04:00
|
|
|
session.ID = strings.TrimRight(base32.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(32)), "=")
|
|
|
|
}
|
2017-07-22 07:18:03 +03:00
|
|
|
if err := s.save(ctx, session); err != nil {
|
2014-06-13 22:36:36 +04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
encoded, err := securecookie.EncodeMulti(session.Name(), session.ID, s.codecs...)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-07-22 07:18:03 +03:00
|
|
|
sessions.SetCookie(ctx, session.Name(), encoded)
|
2014-06-13 22:36:36 +04:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// load loads a session data from the database.
|
|
|
|
// True is returned if there is a session data in the database.
|
2014-06-14 20:29:02 +04:00
|
|
|
func (s *Store) load(session *sessions.Session) (bool, error) {
|
2014-06-13 22:36:36 +04:00
|
|
|
// exists represents whether a session data exists or not.
|
|
|
|
var exists bool
|
2014-06-14 20:15:10 +04:00
|
|
|
err := s.db.View(func(tx *bolt.Tx) error {
|
2014-06-13 22:36:36 +04:00
|
|
|
id := []byte(session.ID)
|
|
|
|
bucket := tx.Bucket(s.config.DBOptions.BucketName)
|
|
|
|
// Get the session data.
|
|
|
|
data := bucket.Get(id)
|
|
|
|
if data == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2014-06-14 20:15:10 +04:00
|
|
|
sessionData, err := shared.Session(data)
|
|
|
|
if err != nil {
|
2014-06-13 22:36:36 +04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
// Check the expiration of the session data.
|
2014-06-14 20:15:10 +04:00
|
|
|
if shared.Expired(sessionData) {
|
|
|
|
err := s.db.Update(func(txu *bolt.Tx) error {
|
|
|
|
return txu.Bucket(s.config.DBOptions.BucketName).Delete(id)
|
|
|
|
})
|
|
|
|
return err
|
2014-06-13 22:36:36 +04:00
|
|
|
}
|
|
|
|
exists = true
|
|
|
|
dec := gob.NewDecoder(bytes.NewBuffer(sessionData.Values))
|
|
|
|
return dec.Decode(&session.Values)
|
|
|
|
})
|
|
|
|
return exists, err
|
|
|
|
}
|
|
|
|
|
2021-05-19 06:39:18 +03:00
|
|
|
// Remove removes the key-value from the database.
|
|
|
|
func (s *Store) Remove(sessionID string) error {
|
|
|
|
return s.db.Update(func(tx *bolt.Tx) error {
|
|
|
|
return tx.Bucket(s.config.DBOptions.BucketName).Delete([]byte(sessionID))
|
2014-06-13 22:36:36 +04:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// save stores the session data in the database.
|
2017-07-22 07:18:03 +03:00
|
|
|
func (s *Store) save(ctx echo.Context, session *sessions.Session) error {
|
2014-06-13 22:36:36 +04:00
|
|
|
var buf bytes.Buffer
|
|
|
|
enc := gob.NewEncoder(&buf)
|
|
|
|
err := enc.Encode(session.Values)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-07-22 07:18:03 +03:00
|
|
|
maxAge := ctx.CookieOptions().MaxAge
|
|
|
|
if maxAge == 0 {
|
|
|
|
maxAge = shared.DefaultMaxAge
|
|
|
|
}
|
|
|
|
data, err := proto.Marshal(shared.NewSession(buf.Bytes(), maxAge))
|
2014-06-13 22:36:36 +04:00
|
|
|
if err != nil {
|
2014-06-17 19:19:04 +04:00
|
|
|
return err
|
2014-06-13 22:36:36 +04:00
|
|
|
}
|
|
|
|
err = s.db.Update(func(tx *bolt.Tx) error {
|
|
|
|
return tx.Bucket(s.config.DBOptions.BucketName).Put([]byte(session.ID), data)
|
|
|
|
})
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// New creates and returns a session store.
|
2014-06-14 20:29:02 +04:00
|
|
|
func New(db *bolt.DB, config Config, keyPairs ...[]byte) (*Store, error) {
|
2014-06-13 22:36:36 +04:00
|
|
|
config.setDefault()
|
2014-06-14 20:29:02 +04:00
|
|
|
store := &Store{
|
2014-06-13 22:36:36 +04:00
|
|
|
codecs: securecookie.CodecsFromPairs(keyPairs...),
|
|
|
|
config: config,
|
2014-06-14 20:15:10 +04:00
|
|
|
db: db,
|
2014-06-13 22:36:36 +04:00
|
|
|
}
|
2014-06-14 20:15:10 +04:00
|
|
|
err := db.Update(func(tx *bolt.Tx) error {
|
|
|
|
_, err := tx.CreateBucketIfNotExists(config.DBOptions.BucketName)
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
if err != nil {
|
2014-06-13 22:36:36 +04:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return store, nil
|
|
|
|
}
|