github.com/gofiber/fiber/v2@v2.47.0/middleware/session/session.go (about)

     1  package session
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/gob"
     6  	"fmt"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/gofiber/fiber/v2"
    11  	"github.com/gofiber/fiber/v2/utils"
    12  
    13  	"github.com/valyala/fasthttp"
    14  )
    15  
    16  type Session struct {
    17  	id         string        // session id
    18  	fresh      bool          // if new session
    19  	ctx        *fiber.Ctx    // fiber context
    20  	config     *Store        // store configuration
    21  	data       *data         // key value data
    22  	byteBuffer *bytes.Buffer // byte buffer for the en- and decode
    23  	exp        time.Duration // expiration of this session
    24  }
    25  
    26  var sessionPool = sync.Pool{
    27  	New: func() interface{} {
    28  		return new(Session)
    29  	},
    30  }
    31  
    32  func acquireSession() *Session {
    33  	s := sessionPool.Get().(*Session) //nolint:forcetypeassert,errcheck // We store nothing else in the pool
    34  	if s.data == nil {
    35  		s.data = acquireData()
    36  	}
    37  	if s.byteBuffer == nil {
    38  		s.byteBuffer = new(bytes.Buffer)
    39  	}
    40  	s.fresh = true
    41  	return s
    42  }
    43  
    44  func releaseSession(s *Session) {
    45  	s.id = ""
    46  	s.exp = 0
    47  	s.ctx = nil
    48  	s.config = nil
    49  	if s.data != nil {
    50  		s.data.Reset()
    51  	}
    52  	if s.byteBuffer != nil {
    53  		s.byteBuffer.Reset()
    54  	}
    55  	sessionPool.Put(s)
    56  }
    57  
    58  // Fresh is true if the current session is new
    59  func (s *Session) Fresh() bool {
    60  	return s.fresh
    61  }
    62  
    63  // ID returns the session id
    64  func (s *Session) ID() string {
    65  	return s.id
    66  }
    67  
    68  // Get will return the value
    69  func (s *Session) Get(key string) interface{} {
    70  	// Better safe than sorry
    71  	if s.data == nil {
    72  		return nil
    73  	}
    74  	return s.data.Get(key)
    75  }
    76  
    77  // Set will update or create a new key value
    78  func (s *Session) Set(key string, val interface{}) {
    79  	// Better safe than sorry
    80  	if s.data == nil {
    81  		return
    82  	}
    83  	s.data.Set(key, val)
    84  }
    85  
    86  // Delete will delete the value
    87  func (s *Session) Delete(key string) {
    88  	// Better safe than sorry
    89  	if s.data == nil {
    90  		return
    91  	}
    92  	s.data.Delete(key)
    93  }
    94  
    95  // Destroy will delete the session from Storage and expire session cookie
    96  func (s *Session) Destroy() error {
    97  	// Better safe than sorry
    98  	if s.data == nil {
    99  		return nil
   100  	}
   101  
   102  	// Reset local data
   103  	s.data.Reset()
   104  
   105  	// Use external Storage if exist
   106  	if err := s.config.Storage.Delete(s.id); err != nil {
   107  		return err
   108  	}
   109  
   110  	// Expire session
   111  	s.delSession()
   112  	return nil
   113  }
   114  
   115  // Regenerate generates a new session id and delete the old one from Storage
   116  func (s *Session) Regenerate() error {
   117  	// Delete old id from storage
   118  	if err := s.config.Storage.Delete(s.id); err != nil {
   119  		return err
   120  	}
   121  
   122  	// Generate a new session, and set session.fresh to true
   123  	s.refresh()
   124  
   125  	return nil
   126  }
   127  
   128  // refresh generates a new session, and set session.fresh to be true
   129  func (s *Session) refresh() {
   130  	// Create a new id
   131  	s.id = s.config.KeyGenerator()
   132  
   133  	// We assign a new id to the session, so the session must be fresh
   134  	s.fresh = true
   135  }
   136  
   137  // Save will update the storage and client cookie
   138  func (s *Session) Save() error {
   139  	// Better safe than sorry
   140  	if s.data == nil {
   141  		return nil
   142  	}
   143  
   144  	// Check if session has your own expiration, otherwise use default value
   145  	if s.exp <= 0 {
   146  		s.exp = s.config.Expiration
   147  	}
   148  
   149  	// Update client cookie
   150  	s.setSession()
   151  
   152  	// Convert data to bytes
   153  	mux.Lock()
   154  	defer mux.Unlock()
   155  	encCache := gob.NewEncoder(s.byteBuffer)
   156  	err := encCache.Encode(&s.data.Data)
   157  	if err != nil {
   158  		return fmt.Errorf("failed to encode data: %w", err)
   159  	}
   160  
   161  	// copy the data in buffer
   162  	encodedBytes := make([]byte, s.byteBuffer.Len())
   163  	copy(encodedBytes, s.byteBuffer.Bytes())
   164  
   165  	// pass copied bytes with session id to provider
   166  	if err := s.config.Storage.Set(s.id, encodedBytes, s.exp); err != nil {
   167  		return err
   168  	}
   169  
   170  	// Release session
   171  	// TODO: It's not safe to use the Session after called Save()
   172  	releaseSession(s)
   173  
   174  	return nil
   175  }
   176  
   177  // Keys will retrieve all keys in current session
   178  func (s *Session) Keys() []string {
   179  	if s.data == nil {
   180  		return []string{}
   181  	}
   182  	return s.data.Keys()
   183  }
   184  
   185  // SetExpiry sets a specific expiration for this session
   186  func (s *Session) SetExpiry(exp time.Duration) {
   187  	s.exp = exp
   188  }
   189  
   190  func (s *Session) setSession() {
   191  	if s.config.source == SourceHeader {
   192  		s.ctx.Request().Header.SetBytesV(s.config.sessionName, []byte(s.id))
   193  		s.ctx.Response().Header.SetBytesV(s.config.sessionName, []byte(s.id))
   194  	} else {
   195  		fcookie := fasthttp.AcquireCookie()
   196  		fcookie.SetKey(s.config.sessionName)
   197  		fcookie.SetValue(s.id)
   198  		fcookie.SetPath(s.config.CookiePath)
   199  		fcookie.SetDomain(s.config.CookieDomain)
   200  		// Cookies are also session cookies if they do not specify the Expires or Max-Age attribute.
   201  		// refer: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
   202  		if !s.config.CookieSessionOnly {
   203  			fcookie.SetMaxAge(int(s.exp.Seconds()))
   204  			fcookie.SetExpire(time.Now().Add(s.exp))
   205  		}
   206  		fcookie.SetSecure(s.config.CookieSecure)
   207  		fcookie.SetHTTPOnly(s.config.CookieHTTPOnly)
   208  
   209  		switch utils.ToLower(s.config.CookieSameSite) {
   210  		case "strict":
   211  			fcookie.SetSameSite(fasthttp.CookieSameSiteStrictMode)
   212  		case "none":
   213  			fcookie.SetSameSite(fasthttp.CookieSameSiteNoneMode)
   214  		default:
   215  			fcookie.SetSameSite(fasthttp.CookieSameSiteLaxMode)
   216  		}
   217  		s.ctx.Response().Header.SetCookie(fcookie)
   218  		fasthttp.ReleaseCookie(fcookie)
   219  	}
   220  }
   221  
   222  func (s *Session) delSession() {
   223  	if s.config.source == SourceHeader {
   224  		s.ctx.Request().Header.Del(s.config.sessionName)
   225  		s.ctx.Response().Header.Del(s.config.sessionName)
   226  	} else {
   227  		s.ctx.Request().Header.DelCookie(s.config.sessionName)
   228  		s.ctx.Response().Header.DelCookie(s.config.sessionName)
   229  
   230  		fcookie := fasthttp.AcquireCookie()
   231  		fcookie.SetKey(s.config.sessionName)
   232  		fcookie.SetPath(s.config.CookiePath)
   233  		fcookie.SetDomain(s.config.CookieDomain)
   234  		fcookie.SetMaxAge(-1)
   235  		fcookie.SetExpire(time.Now().Add(-1 * time.Minute))
   236  		fcookie.SetSecure(s.config.CookieSecure)
   237  		fcookie.SetHTTPOnly(s.config.CookieHTTPOnly)
   238  
   239  		switch utils.ToLower(s.config.CookieSameSite) {
   240  		case "strict":
   241  			fcookie.SetSameSite(fasthttp.CookieSameSiteStrictMode)
   242  		case "none":
   243  			fcookie.SetSameSite(fasthttp.CookieSameSiteNoneMode)
   244  		default:
   245  			fcookie.SetSameSite(fasthttp.CookieSameSiteLaxMode)
   246  		}
   247  
   248  		s.ctx.Response().Header.SetCookie(fcookie)
   249  		fasthttp.ReleaseCookie(fcookie)
   250  	}
   251  }