mirror of https://github.com/go-gitea/gitea.git
252 lines
6.6 KiB
Go
252 lines
6.6 KiB
Go
/*
|
|
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
|
|
}
|
|
}
|
|
}
|