mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 18:51:03 +01:00
Merge pull request #693 from corebreaker/session-expiration
Fix up for sessions Former-commit-id: 12b18902b4776335053b4d971ec564a9659a4c2d
This commit is contained in:
commit
351f099ad6
|
@ -65,5 +65,10 @@ func main() {
|
||||||
sess.Destroy(ctx)
|
sess.Destroy(ctx)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.Get("/update", func(ctx context.Context) {
|
||||||
|
// updates expire date with a new date
|
||||||
|
sess.ShiftExpiraton(ctx)
|
||||||
|
})
|
||||||
|
|
||||||
app.Run(iris.Addr(":8080"))
|
app.Run(iris.Addr(":8080"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,10 +62,16 @@ func newApp() *iris.Application {
|
||||||
mySessions.Start(ctx).Clear()
|
mySessions.Start(ctx).Clear()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.Get("/update", func(ctx context.Context) {
|
||||||
|
// updates expire date with a new date
|
||||||
|
mySessions.ShiftExpiraton(ctx)
|
||||||
|
})
|
||||||
|
|
||||||
app.Get("/destroy", func(ctx context.Context) {
|
app.Get("/destroy", func(ctx context.Context) {
|
||||||
//destroy, removes the entire session data and cookie
|
//destroy, removes the entire session data and cookie
|
||||||
mySessions.Destroy(ctx)
|
mySessions.Destroy(ctx)
|
||||||
}) // Note about destroy:
|
})
|
||||||
|
// Note about destroy:
|
||||||
//
|
//
|
||||||
// You can destroy a session outside of a handler too, using the:
|
// You can destroy a session outside of a handler too, using the:
|
||||||
// mySessions.DestroyByID
|
// mySessions.DestroyByID
|
||||||
|
|
|
@ -69,6 +69,11 @@ func main() {
|
||||||
sess.Start(ctx).Clear()
|
sess.Start(ctx).Clear()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.Get("/update", func(ctx context.Context) {
|
||||||
|
// updates expire date
|
||||||
|
sess.ShiftExpiraton(ctx)
|
||||||
|
})
|
||||||
|
|
||||||
app.Get("/destroy", func(ctx context.Context) {
|
app.Get("/destroy", func(ctx context.Context) {
|
||||||
|
|
||||||
//destroy, removes the entire session data and cookie
|
//destroy, removes the entire session data and cookie
|
||||||
|
|
|
@ -1,17 +1,24 @@
|
||||||
package sessions
|
package sessions
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
// Database is the interface which all session databases should implement
|
// Database is the interface which all session databases should implement
|
||||||
// By design it doesn't support any type of cookie session like other frameworks,
|
// By design it doesn't support any type of cookie session like other frameworks,
|
||||||
// I want to protect you, believe me, no context access (although we could)
|
// I want to protect you, believe me, no context access (although we could)
|
||||||
// The scope of the database is to session somewhere the sessions in order to
|
// The scope of the database is to session somewhere the sessions in order to
|
||||||
// keep them after restarting the server, nothing more.
|
// keep them after restarting the server, nothing more.
|
||||||
// the values are sessiond by the underline session, the check for new sessions, or
|
// the values are sessions by the underline session, the check for new sessions, or
|
||||||
// 'this session value should added' are made automatically you are able just to set the values to your backend database with Load function.
|
// 'this session value should added' are made automatically
|
||||||
|
// you are able just to set the values to your backend database with Load function.
|
||||||
// session database doesn't have any write or read access to the session, the loading of
|
// session database doesn't have any write or read access to the session, the loading of
|
||||||
// the initial data is done by the Load(string) map[string]interfface{} function
|
// the initial data is done by the Load(string) (map[string]interfface{}, *time.Time) function
|
||||||
// synchronization are made automatically, you can register more than one session database
|
// synchronization are made automatically, you can register more than one session database
|
||||||
// but the first non-empty Load return data will be used as the session values.
|
// but the first non-empty Load return data will be used as the session values.
|
||||||
|
// The Expire Date is given with data to save because the session entry must keep trace
|
||||||
|
// of the expire date in the case of the server is restarted. So the server will recover
|
||||||
|
// expiration state of session entry and it will track the expiration again.
|
||||||
|
// If expireDate is nil, that's means that there is no expire date.
|
||||||
type Database interface {
|
type Database interface {
|
||||||
Load(string) map[string]interface{}
|
Load(sid string) (datas map[string]interface{}, expireDate *time.Time)
|
||||||
Update(string, map[string]interface{})
|
Update(sid string, datas map[string]interface{}, expireDate *time.Time)
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,43 +36,76 @@ func (p *provider) RegisterDatabase(db Database) {
|
||||||
p.mu.Unlock()
|
p.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// newSession returns a new session from sessionid
|
// startAutoDestroy start a task which destoy the session when expire date is reached,
|
||||||
func (p *provider) newSession(sid string, expires time.Duration) *Session {
|
// but only if `expires` parameter is positive. It updates the expire date of the session from `expires` parameter.
|
||||||
sess := &Session{
|
func (p *provider) startAutoDestroy(s *Session, expires time.Duration) bool {
|
||||||
sid: sid,
|
res := expires > 0
|
||||||
provider: p,
|
if res { // if not unlimited life duration and no -1 (cookie remove action is based on browser's session)
|
||||||
values: p.loadSessionValuesFromDB(sid),
|
expireDate := time.Now().Add(expires)
|
||||||
flashes: make(map[string]*flashMessage),
|
|
||||||
}
|
|
||||||
|
|
||||||
if expires > 0 { // if not unlimited life duration and no -1 (cookie remove action is based on browser's session)
|
s.expireAt = &expireDate
|
||||||
time.AfterFunc(expires, func() {
|
s.timer = time.AfterFunc(expires, func() {
|
||||||
// the destroy makes the check if this session is exists then or not,
|
// the destroy makes the check if this session is exists then or not,
|
||||||
// this is used to destroy the session from the server-side also
|
// this is used to destroy the session from the server-side also
|
||||||
// it's good to have here for security reasons, I didn't add it on the gc function to separate its action
|
// it's good to have here for security reasons, I didn't add it on the gc function to separate its action
|
||||||
p.Destroy(sid)
|
p.Destroy(s.sid)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// newSession returns a new session from sessionid
|
||||||
|
func (p *provider) newSession(sid string, expires time.Duration) *Session {
|
||||||
|
values, expireAt := p.loadSessionValuesFromDB(sid)
|
||||||
|
|
||||||
|
sess := &Session{
|
||||||
|
sid: sid,
|
||||||
|
provider: p,
|
||||||
|
values: values,
|
||||||
|
flashes: make(map[string]*flashMessage),
|
||||||
|
expireAt: expireAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len(values) > 0) && (sess.expireAt != nil) {
|
||||||
|
// Restore expiration state
|
||||||
|
// However, if session save in database has no expiration date,
|
||||||
|
// therefore the expiration will be reinitialised with session configuration
|
||||||
|
expires = sess.expireAt.Sub(time.Now())
|
||||||
|
}
|
||||||
|
|
||||||
|
p.startAutoDestroy(sess, expires)
|
||||||
|
|
||||||
return sess
|
return sess
|
||||||
}
|
}
|
||||||
|
|
||||||
// can return nil
|
// can return nil memstore
|
||||||
func (p *provider) loadSessionValuesFromDB(sid string) memstore.Store {
|
func (p *provider) loadSessionValuesFromDB(sid string) (memstore.Store, *time.Time) {
|
||||||
var store memstore.Store
|
var store memstore.Store
|
||||||
|
var expireDate *time.Time
|
||||||
|
|
||||||
for i, n := 0, len(p.databases); i < n; i++ {
|
for i, n := 0, len(p.databases); i < n; i++ {
|
||||||
if dbValues := p.databases[i].Load(sid); dbValues != nil && len(dbValues) > 0 {
|
dbValues, currentExpireDate := p.databases[i].Load(sid)
|
||||||
|
if dbValues != nil && len(dbValues) > 0 {
|
||||||
for k, v := range dbValues {
|
for k, v := range dbValues {
|
||||||
store.Set(k, v)
|
store.Set(k, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentExpireDate != nil) && ((expireDate == nil) || expireDate.After(*currentExpireDate)) {
|
||||||
|
expireDate = currentExpireDate
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return store
|
|
||||||
|
// Check if session has already expired
|
||||||
|
if (expireDate != nil) && expireDate.Before(time.Now()) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return store, expireDate
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *provider) updateDatabases(sid string, store memstore.Store) {
|
func (p *provider) updateDatabases(sess *Session, store memstore.Store) {
|
||||||
|
|
||||||
if l := store.Len(); l > 0 {
|
if l := store.Len(); l > 0 {
|
||||||
mapValues := make(map[string]interface{}, l)
|
mapValues := make(map[string]interface{}, l)
|
||||||
|
|
||||||
|
@ -81,7 +114,7 @@ func (p *provider) updateDatabases(sid string, store memstore.Store) {
|
||||||
})
|
})
|
||||||
|
|
||||||
for i, n := 0, len(p.databases); i < n; i++ {
|
for i, n := 0, len(p.databases); i < n; i++ {
|
||||||
p.databases[i].Update(sid, mapValues)
|
p.databases[i].Update(sess.sid, mapValues, sess.expireAt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,17 +128,50 @@ func (p *provider) Init(sid string, expires time.Duration) *Session {
|
||||||
return newSession
|
return newSession
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateExpiraton update expire date of a session, plus it updates destroy task
|
||||||
|
func (p *provider) UpdateExpiraton(sid string, expires time.Duration) (done bool) {
|
||||||
|
if expires <= 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
p.mu.Lock()
|
||||||
|
sess, found := p.sessions[sid]
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if sess.timer == nil {
|
||||||
|
return p.startAutoDestroy(sess, expires)
|
||||||
|
} else {
|
||||||
|
if expires <= 0 {
|
||||||
|
sess.timer.Stop()
|
||||||
|
sess.timer = nil
|
||||||
|
sess.expireAt = nil
|
||||||
|
} else {
|
||||||
|
expireDate := time.Now().Add(expires)
|
||||||
|
|
||||||
|
sess.expireAt = &expireDate
|
||||||
|
sess.timer.Reset(expires)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Read returns the store which sid parameter belongs
|
// Read returns the store which sid parameter belongs
|
||||||
func (p *provider) Read(sid string, expires time.Duration) *Session {
|
func (p *provider) Read(sid string, expires time.Duration) *Session {
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
if sess, found := p.sessions[sid]; found {
|
if sess, found := p.sessions[sid]; found {
|
||||||
sess.runFlashGC() // run the flash messages GC, new request here of existing session
|
sess.runFlashGC() // run the flash messages GC, new request here of existing session
|
||||||
p.mu.Unlock()
|
p.mu.Unlock()
|
||||||
|
|
||||||
return sess
|
return sess
|
||||||
}
|
}
|
||||||
p.mu.Unlock()
|
p.mu.Unlock()
|
||||||
return p.Init(sid, expires) // if not found create new
|
|
||||||
|
|
||||||
|
return p.Init(sid, expires) // if not found create new
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destroy destroys the session, removes all sessions and flash values,
|
// Destroy destroys the session, removes all sessions and flash values,
|
||||||
|
@ -116,8 +182,12 @@ func (p *provider) Destroy(sid string) {
|
||||||
if sess, found := p.sessions[sid]; found {
|
if sess, found := p.sessions[sid]; found {
|
||||||
sess.values = nil
|
sess.values = nil
|
||||||
sess.flashes = nil
|
sess.flashes = nil
|
||||||
|
if sess.timer != nil {
|
||||||
|
sess.timer.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
delete(p.sessions, sid)
|
delete(p.sessions, sid)
|
||||||
p.updateDatabases(sid, nil)
|
p.updateDatabases(sess, nil)
|
||||||
}
|
}
|
||||||
p.mu.Unlock()
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
@ -129,8 +199,12 @@ func (p *provider) Destroy(sid string) {
|
||||||
func (p *provider) DestroyAll() {
|
func (p *provider) DestroyAll() {
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
for _, sess := range p.sessions {
|
for _, sess := range p.sessions {
|
||||||
|
if sess.timer != nil {
|
||||||
|
sess.timer.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
delete(p.sessions, sess.ID())
|
delete(p.sessions, sess.ID())
|
||||||
p.updateDatabases(sess.ID(), nil)
|
p.updateDatabases(sess, nil)
|
||||||
}
|
}
|
||||||
p.mu.Unlock()
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ type (
|
||||||
// This is what will be returned when sess := sessions.Start().
|
// This is what will be returned when sess := sessions.Start().
|
||||||
Session struct {
|
Session struct {
|
||||||
sid string
|
sid string
|
||||||
|
isNew bool
|
||||||
values memstore.Store // here are the real values
|
values memstore.Store // here are the real values
|
||||||
// we could set the flash messages inside values but this will bring us more problems
|
// we could set the flash messages inside values but this will bring us more problems
|
||||||
// because of session databases and because of
|
// because of session databases and because of
|
||||||
|
@ -26,7 +27,8 @@ type (
|
||||||
// NOTE: flashes are not managed by third-party, only inside session struct.
|
// NOTE: flashes are not managed by third-party, only inside session struct.
|
||||||
flashes map[string]*flashMessage
|
flashes map[string]*flashMessage
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
createdAt time.Time
|
expireAt *time.Time // nil pointer means no expire date
|
||||||
|
timer *time.Timer
|
||||||
provider *provider
|
provider *provider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,6 +44,27 @@ func (s *Session) ID() string {
|
||||||
return s.sid
|
return s.sid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsNew returns true if is's a new session
|
||||||
|
func (s *Session) IsNew() bool {
|
||||||
|
return s.isNew
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasExpireDate test if this session has an expire date, if not, this session never expires
|
||||||
|
func (s *Session) HasExpireDate() bool {
|
||||||
|
return s.expireAt != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExpireDate get the expire date, if this session has no expire date, the returned value has the zero value
|
||||||
|
func (s *Session) GetExpireDate() time.Time {
|
||||||
|
var res time.Time
|
||||||
|
|
||||||
|
if s.expireAt != nil {
|
||||||
|
res = *s.expireAt
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
// Get returns a value based on its "key".
|
// Get returns a value based on its "key".
|
||||||
func (s *Session) Get(key string) interface{} {
|
func (s *Session) Get(key string) interface{} {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
|
@ -267,6 +290,7 @@ func (s *Session) set(key string, value interface{}, immutable bool) {
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
|
||||||
s.updateDatabases()
|
s.updateDatabases()
|
||||||
|
s.isNew = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set fills the session with an entry"value", based on its "key".
|
// Set fills the session with an entry"value", based on its "key".
|
||||||
|
@ -318,11 +342,15 @@ func (s *Session) Delete(key string) bool {
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
|
||||||
s.updateDatabases()
|
s.updateDatabases()
|
||||||
|
if removed {
|
||||||
|
s.isNew = false
|
||||||
|
}
|
||||||
|
|
||||||
return removed
|
return removed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Session) updateDatabases() {
|
func (s *Session) updateDatabases() {
|
||||||
s.provider.updateDatabases(s.sid, s.values)
|
s.provider.updateDatabases(s, s.values)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteFlash removes a flash message by its key.
|
// DeleteFlash removes a flash message by its key.
|
||||||
|
@ -339,6 +367,7 @@ func (s *Session) Clear() {
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
|
||||||
s.updateDatabases()
|
s.updateDatabases()
|
||||||
|
s.isNew = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClearFlashes removes all flash messages.
|
// ClearFlashes removes all flash messages.
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package redis
|
package redis
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
|
|
||||||
|
@ -23,7 +24,7 @@ func (d *Database) Config() *service.Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load loads the values to the underline.
|
// Load loads the values to the underline.
|
||||||
func (d *Database) Load(sid string) map[string]interface{} {
|
func (d *Database) Load(sid string) (datas map[string]interface{}, expireDate *time.Time) {
|
||||||
values := make(map[string]interface{})
|
values := make(map[string]interface{})
|
||||||
|
|
||||||
if !d.redis.Connected { //yes, check every first time's session for valid redis connection
|
if !d.redis.Connected { //yes, check every first time's session for valid redis connection
|
||||||
|
@ -38,6 +39,7 @@ func (d *Database) Load(sid string) map[string]interface{} {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//fetch the values from this session id and copy-> store them
|
//fetch the values from this session id and copy-> store them
|
||||||
val, err := d.redis.GetBytes(sid)
|
val, err := d.redis.GetBytes(sid)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -45,8 +47,19 @@ func (d *Database) Load(sid string) map[string]interface{} {
|
||||||
DeserializeBytes(val, &values)
|
DeserializeBytes(val, &values)
|
||||||
}
|
}
|
||||||
|
|
||||||
return values
|
datas, _ = values["session-data"].(map[string]interface{})
|
||||||
|
|
||||||
|
dbExpireDateValue, exists := values["expire-date"]
|
||||||
|
if !exists {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
expireDateValue, ok := dbExpireDateValue.(time.Time)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return datas, &expireDateValue
|
||||||
}
|
}
|
||||||
|
|
||||||
// serialize the values to be stored as strings inside the Redis, we panic at any serialization error here
|
// serialize the values to be stored as strings inside the Redis, we panic at any serialization error here
|
||||||
|
@ -60,11 +73,17 @@ func serialize(values map[string]interface{}) []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update updates the real redis store
|
// Update updates the real redis store
|
||||||
func (d *Database) Update(sid string, newValues map[string]interface{}) {
|
func (d *Database) Update(sid string, newValues map[string]interface{}, expireDate *time.Time) {
|
||||||
if len(newValues) == 0 {
|
if len(newValues) == 0 {
|
||||||
go d.redis.Delete(sid)
|
go d.redis.Delete(sid)
|
||||||
} else {
|
} else {
|
||||||
go d.redis.Set(sid, serialize(newValues)) //set/update all the values
|
datas := map[string]interface{}{"session-data": newValues}
|
||||||
|
if expireDate != nil {
|
||||||
|
datas["expire-date"] = *expireDate
|
||||||
|
}
|
||||||
|
|
||||||
|
//set/update all the values
|
||||||
|
go d.redis.Set(sid, serialize(datas))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,70 @@ func (s *Sessions) UseDatabase(db Database) {
|
||||||
s.provider.RegisterDatabase(db)
|
s.provider.RegisterDatabase(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// updateCookie gains the ability of updating the session browser cookie to any method which wants to update it
|
||||||
|
func (s *Sessions) updateCookie(sid string, ctx context.Context, expires time.Duration) {
|
||||||
|
cookie := &http.Cookie{}
|
||||||
|
|
||||||
|
// The RFC makes no mention of encoding url value, so here I think to encode both sessionid key and the value using the safe(to put and to use as cookie) url-encoding
|
||||||
|
cookie.Name = s.config.Cookie
|
||||||
|
|
||||||
|
cookie.Value = sid
|
||||||
|
cookie.Path = "/"
|
||||||
|
if !s.config.DisableSubdomainPersistence {
|
||||||
|
|
||||||
|
requestDomain := ctx.Request().URL.Host
|
||||||
|
if portIdx := strings.IndexByte(requestDomain, ':'); portIdx > 0 {
|
||||||
|
requestDomain = requestDomain[0:portIdx]
|
||||||
|
}
|
||||||
|
if IsValidCookieDomain(requestDomain) {
|
||||||
|
|
||||||
|
// RFC2109, we allow level 1 subdomains, but no further
|
||||||
|
// if we have localhost.com , we want the localhost.cos.
|
||||||
|
// so if we have something like: mysubdomain.localhost.com we want the localhost here
|
||||||
|
// if we have mysubsubdomain.mysubdomain.localhost.com we want the .mysubdomain.localhost.com here
|
||||||
|
// slow things here, especially the 'replace' but this is a good and understable( I hope) way to get the be able to set cookies from subdomains & domain with 1-level limit
|
||||||
|
if dotIdx := strings.LastIndexByte(requestDomain, '.'); dotIdx > 0 {
|
||||||
|
// is mysubdomain.localhost.com || mysubsubdomain.mysubdomain.localhost.com
|
||||||
|
s := requestDomain[0:dotIdx] // set mysubdomain.localhost || mysubsubdomain.mysubdomain.localhost
|
||||||
|
if secondDotIdx := strings.LastIndexByte(s, '.'); secondDotIdx > 0 {
|
||||||
|
//is mysubdomain.localhost || mysubsubdomain.mysubdomain.localhost
|
||||||
|
s = s[secondDotIdx+1:] // set to localhost || mysubdomain.localhost
|
||||||
|
}
|
||||||
|
// replace the s with the requestDomain before the domain's siffux
|
||||||
|
subdomainSuff := strings.LastIndexByte(requestDomain, '.')
|
||||||
|
if subdomainSuff > len(s) { // if it is actual exists as subdomain suffix
|
||||||
|
requestDomain = strings.Replace(requestDomain, requestDomain[0:subdomainSuff], s, 1) // set to localhost.com || mysubdomain.localhost.com
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// finally set the .localhost.com (for(1-level) || .mysubdomain.localhost.com (for 2-level subdomain allow)
|
||||||
|
cookie.Domain = "." + requestDomain // . to allow persistence
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie.HttpOnly = true
|
||||||
|
// MaxAge=0 means no 'Max-Age' attribute specified.
|
||||||
|
// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
|
||||||
|
// MaxAge>0 means Max-Age attribute present and given in seconds
|
||||||
|
if expires >= 0 {
|
||||||
|
if expires == 0 { // unlimited life
|
||||||
|
cookie.Expires = CookieExpireUnlimited
|
||||||
|
} else { // > 0
|
||||||
|
cookie.Expires = time.Now().Add(expires)
|
||||||
|
}
|
||||||
|
cookie.MaxAge = int(cookie.Expires.Sub(time.Now()).Seconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the cookie to secure if this is a tls wrapped request
|
||||||
|
// and the configuration allows it.
|
||||||
|
if ctx.Request().TLS != nil && s.config.CookieSecureTLS {
|
||||||
|
cookie.Secure = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// encode the session id cookie client value right before send it.
|
||||||
|
cookie.Value = s.encodeCookieValue(cookie.Value)
|
||||||
|
AddCookie(ctx, cookie)
|
||||||
|
}
|
||||||
|
|
||||||
// Start should start the session for the particular request.
|
// Start should start the session for the particular request.
|
||||||
func (s *Sessions) Start(ctx context.Context) *Session {
|
func (s *Sessions) Start(ctx context.Context) *Session {
|
||||||
cookieValue := GetCookie(ctx, s.config.Cookie)
|
cookieValue := GetCookie(ctx, s.config.Cookie)
|
||||||
|
@ -43,74 +107,34 @@ func (s *Sessions) Start(ctx context.Context) *Session {
|
||||||
sid := s.config.SessionIDGenerator()
|
sid := s.config.SessionIDGenerator()
|
||||||
|
|
||||||
sess := s.provider.Init(sid, s.config.Expires)
|
sess := s.provider.Init(sid, s.config.Expires)
|
||||||
cookie := &http.Cookie{}
|
sess.isNew = len(sess.values) == 0
|
||||||
|
|
||||||
// The RFC makes no mention of encoding url value, so here I think to encode both sessionid key and the value using the safe(to put and to use as cookie) url-encoding
|
s.updateCookie(sid, ctx, s.config.Expires)
|
||||||
cookie.Name = s.config.Cookie
|
|
||||||
|
|
||||||
cookie.Value = sid
|
|
||||||
cookie.Path = "/"
|
|
||||||
if !s.config.DisableSubdomainPersistence {
|
|
||||||
|
|
||||||
requestDomain := ctx.Request().URL.Host
|
|
||||||
if portIdx := strings.IndexByte(requestDomain, ':'); portIdx > 0 {
|
|
||||||
requestDomain = requestDomain[0:portIdx]
|
|
||||||
}
|
|
||||||
if IsValidCookieDomain(requestDomain) {
|
|
||||||
|
|
||||||
// RFC2109, we allow level 1 subdomains, but no further
|
|
||||||
// if we have localhost.com , we want the localhost.cos.
|
|
||||||
// so if we have something like: mysubdomain.localhost.com we want the localhost here
|
|
||||||
// if we have mysubsubdomain.mysubdomain.localhost.com we want the .mysubdomain.localhost.com here
|
|
||||||
// slow things here, especially the 'replace' but this is a good and understable( I hope) way to get the be able to set cookies from subdomains & domain with 1-level limit
|
|
||||||
if dotIdx := strings.LastIndexByte(requestDomain, '.'); dotIdx > 0 {
|
|
||||||
// is mysubdomain.localhost.com || mysubsubdomain.mysubdomain.localhost.com
|
|
||||||
s := requestDomain[0:dotIdx] // set mysubdomain.localhost || mysubsubdomain.mysubdomain.localhost
|
|
||||||
if secondDotIdx := strings.LastIndexByte(s, '.'); secondDotIdx > 0 {
|
|
||||||
//is mysubdomain.localhost || mysubsubdomain.mysubdomain.localhost
|
|
||||||
s = s[secondDotIdx+1:] // set to localhost || mysubdomain.localhost
|
|
||||||
}
|
|
||||||
// replace the s with the requestDomain before the domain's siffux
|
|
||||||
subdomainSuff := strings.LastIndexByte(requestDomain, '.')
|
|
||||||
if subdomainSuff > len(s) { // if it is actual exists as subdomain suffix
|
|
||||||
requestDomain = strings.Replace(requestDomain, requestDomain[0:subdomainSuff], s, 1) // set to localhost.com || mysubdomain.localhost.com
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// finally set the .localhost.com (for(1-level) || .mysubdomain.localhost.com (for 2-level subdomain allow)
|
|
||||||
cookie.Domain = "." + requestDomain // . to allow persistence
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cookie.HttpOnly = true
|
|
||||||
// MaxAge=0 means no 'Max-Age' attribute specified.
|
|
||||||
// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
|
|
||||||
// MaxAge>0 means Max-Age attribute present and given in seconds
|
|
||||||
if s.config.Expires >= 0 {
|
|
||||||
if s.config.Expires == 0 { // unlimited life
|
|
||||||
cookie.Expires = CookieExpireUnlimited
|
|
||||||
} else { // > 0
|
|
||||||
cookie.Expires = time.Now().Add(s.config.Expires)
|
|
||||||
}
|
|
||||||
cookie.MaxAge = int(cookie.Expires.Sub(time.Now()).Seconds())
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the cookie to secure if this is a tls wrapped request
|
|
||||||
// and the configuration allows it.
|
|
||||||
if ctx.Request().TLS != nil && s.config.CookieSecureTLS {
|
|
||||||
cookie.Secure = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// encode the session id cookie client value right before send it.
|
|
||||||
cookie.Value = s.encodeCookieValue(cookie.Value)
|
|
||||||
AddCookie(ctx, cookie)
|
|
||||||
|
|
||||||
return sess
|
return sess
|
||||||
}
|
}
|
||||||
|
|
||||||
cookieValue = s.decodeCookieValue(cookieValue)
|
cookieValue = s.decodeCookieValue(cookieValue)
|
||||||
sess := s.provider.Read(cookieValue, s.config.Expires)
|
sess := s.provider.Read(cookieValue, s.config.Expires)
|
||||||
return sess
|
|
||||||
|
|
||||||
|
return sess
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShiftExpiraton move the expire date of a session to a new date by using session default timeout configuration
|
||||||
|
func (s *Sessions) ShiftExpiraton(ctx context.Context) {
|
||||||
|
s.UpdateExpiraton(ctx, s.config.Expires)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateExpiraton change expire date of a session to a new date by using timeout value passed by `expires` parameter
|
||||||
|
func (s *Sessions) UpdateExpiraton(ctx context.Context, expires time.Duration) {
|
||||||
|
cookieValue := GetCookie(ctx, s.config.Cookie)
|
||||||
|
|
||||||
|
if cookieValue != "" {
|
||||||
|
sid := s.decodeCookieValue(cookieValue)
|
||||||
|
if s.provider.UpdateExpiraton(sid, expires) {
|
||||||
|
s.updateCookie(sid, ctx, expires)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destroy remove the session data and remove the associated cookie.
|
// Destroy remove the session data and remove the associated cookie.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user