123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- /*
- Package xormstore is a XORM backend for gorilla sessions
-
- Simplest form:
-
- store, err := xormstore.New(engine, []byte("secret-hash-key"))
-
- All options:
-
- store, err := xormstore.NewOptions(
- engine, // *xorm.Engine
- xormstore.Options{
- TableName: "sessions", // "sessions" is default
- SkipCreateTable: false, // false is default
- },
- []byte("secret-hash-key"), // 32 or 64 bytes recommended, required
- []byte("secret-encyption-key")) // nil, 16, 24 or 32 bytes, optional
-
- if err != nil {
- // xormstore can not be initialized
- }
-
- // some more settings, see sessions.Options
- store.SessionOpts.Secure = true
- store.SessionOpts.HttpOnly = true
- store.SessionOpts.MaxAge = 60 * 60 * 24 * 60
-
- If you want periodic cleanup of expired sessions:
-
- quit := make(chan struct{})
- go store.PeriodicCleanup(1*time.Hour, quit)
-
- For more information about the keys see https://github.com/gorilla/securecookie
-
- For API to use in HTTP handlers see https://github.com/gorilla/sessions
- */
- package xormstore
-
- import (
- "encoding/base32"
- "net/http"
- "strings"
- "time"
-
- "github.com/lafriks/xormstore/util"
-
- "github.com/go-xorm/xorm"
- "github.com/gorilla/context"
- "github.com/gorilla/securecookie"
- "github.com/gorilla/sessions"
- )
-
- const sessionIDLen = 32
- const defaultTableName = "sessions"
- const defaultMaxAge = 60 * 60 * 24 * 30 // 30 days
- const defaultPath = "/"
-
- // Options for xormstore
- type Options struct {
- TableName string
- SkipCreateTable bool
- }
-
- // Store represent a xormstore
- type Store struct {
- e *xorm.Engine
- opts Options
- Codecs []securecookie.Codec
- SessionOpts *sessions.Options
- }
-
- type xormSession struct {
- ID string `xorm:"VARCHAR(100) PK NAME 'id'"`
- Data string `xorm:"TEXT"`
- CreatedUnix util.TimeStamp `xorm:"created"`
- UpdatedUnix util.TimeStamp `xorm:"updated"`
- ExpiresUnix util.TimeStamp `xorm:"INDEX"`
-
- tableName string `xorm:"-"` // just to store table name for easier access
- }
-
- // Define a type for context keys so that they can't clash with anything else stored in context
- type contextKey string
-
- func (xs *xormSession) TableName() string {
- return xs.tableName
- }
-
- // New creates a new xormstore session
- func New(e *xorm.Engine, keyPairs ...[]byte) (*Store, error) {
- return NewOptions(e, Options{}, keyPairs...)
- }
-
- // NewOptions creates a new xormstore session with options
- func NewOptions(e *xorm.Engine, opts Options, keyPairs ...[]byte) (*Store, error) {
- st := &Store{
- e: e,
- opts: opts,
- Codecs: securecookie.CodecsFromPairs(keyPairs...),
- SessionOpts: &sessions.Options{
- Path: defaultPath,
- MaxAge: defaultMaxAge,
- },
- }
- if st.opts.TableName == "" {
- st.opts.TableName = defaultTableName
- }
-
- if !st.opts.SkipCreateTable {
- if err := st.e.Sync2(&xormSession{tableName: st.opts.TableName}); err != nil {
- return nil, err
- }
- }
-
- return st, nil
- }
-
- // Get returns a session for the given name after adding it to the registry.
- func (st *Store) Get(r *http.Request, name string) (*sessions.Session, error) {
- return sessions.GetRegistry(r).Get(st, name)
- }
-
- // New creates a session with name without adding it to the registry.
- func (st *Store) New(r *http.Request, name string) (*sessions.Session, error) {
- session := sessions.NewSession(st, name)
- opts := *st.SessionOpts
- session.Options = &opts
-
- st.MaxAge(st.SessionOpts.MaxAge)
-
- // try fetch from db if there is a cookie
- if cookie, err := r.Cookie(name); err == nil {
- if err := securecookie.DecodeMulti(name, cookie.Value, &session.ID, st.Codecs...); err != nil {
- return session, nil
- }
- s := &xormSession{tableName: st.opts.TableName}
- if has, err := st.e.Where("id = ? AND expires_unix >= ?", session.ID, util.TimeStampNow()).Get(s); !has || err != nil {
- return session, nil
- }
- if err := securecookie.DecodeMulti(session.Name(), s.Data, &session.Values, st.Codecs...); err != nil {
- return session, nil
- }
-
- context.Set(r, contextKey(name), s)
- }
-
- return session, nil
- }
-
- // Save session and set cookie header
- func (st *Store) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error {
- s, _ := context.Get(r, contextKey(session.Name())).(*xormSession)
-
- // delete if max age is < 0
- if session.Options.MaxAge < 0 {
- if s != nil {
- if _, err := st.e.Delete(&xormSession{
- ID: session.ID,
- tableName: st.opts.TableName,
- }); err != nil {
- return err
- }
- }
- http.SetCookie(w, sessions.NewCookie(session.Name(), "", session.Options))
- return nil
- }
-
- data, err := securecookie.EncodeMulti(session.Name(), session.Values, st.Codecs...)
- if err != nil {
- return err
- }
- now := util.TimeStampNow()
- expire := now.AddDuration(time.Second * time.Duration(session.Options.MaxAge))
-
- if s == nil {
- // generate random session ID key suitable for storage in the db
- session.ID = strings.TrimRight(
- base32.StdEncoding.EncodeToString(
- securecookie.GenerateRandomKey(sessionIDLen)), "=")
- s = &xormSession{
- ID: session.ID,
- Data: data,
- CreatedUnix: now,
- UpdatedUnix: now,
- ExpiresUnix: expire,
- tableName: st.opts.TableName,
- }
- if _, err := st.e.Insert(s); err != nil {
- return err
- }
- context.Set(r, contextKey(session.Name()), s)
- } else {
- s.Data = data
- s.UpdatedUnix = now
- s.ExpiresUnix = expire
- if _, err := st.e.ID(s.ID).Cols("data", "updated_unix", "expires_unix").Update(s); err != nil {
- return err
- }
- }
-
- // set session id cookie
- id, err := securecookie.EncodeMulti(session.Name(), session.ID, st.Codecs...)
- if err != nil {
- return err
- }
- http.SetCookie(w, sessions.NewCookie(session.Name(), id, session.Options))
-
- return nil
- }
-
- // MaxAge sets the maximum age for the store and the underlying cookie
- // implementation. Individual sessions can be deleted by setting
- // Options.MaxAge = -1 for that session.
- func (st *Store) MaxAge(age int) {
- st.SessionOpts.MaxAge = age
- for _, codec := range st.Codecs {
- if sc, ok := codec.(*securecookie.SecureCookie); ok {
- sc.MaxAge(age)
- }
- }
- }
-
- // MaxLength restricts the maximum length of new sessions to l.
- // If l is 0 there is no limit to the size of a session, use with caution.
- // The default is 4096 (default for securecookie)
- func (st *Store) MaxLength(l int) {
- for _, c := range st.Codecs {
- if codec, ok := c.(*securecookie.SecureCookie); ok {
- codec.MaxLength(l)
- }
- }
- }
-
- // Cleanup deletes expired sessions
- func (st *Store) Cleanup() {
- st.e.Where("expires_unix < ?", util.TimeStampNow()).Delete(&xormSession{tableName: st.opts.TableName})
- }
-
- // PeriodicCleanup runs Cleanup every interval. Close quit channel to stop.
- func (st *Store) PeriodicCleanup(interval time.Duration, quit <-chan struct{}) {
- t := time.NewTicker(interval)
- defer t.Stop()
- for {
- select {
- case <-t.C:
- st.Cleanup()
- case <-quit:
- return
- }
- }
- }
|