167 lines
3.7 KiB
Go
167 lines
3.7 KiB
Go
package session
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/andreadipersio/securecookie"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// Key ...
|
|
type Key int
|
|
|
|
const (
|
|
SessionKey Key = iota
|
|
)
|
|
|
|
// Options ...
|
|
type Options struct {
|
|
name string
|
|
secret string
|
|
secure bool
|
|
expiry time.Duration
|
|
}
|
|
|
|
// NewOptions ...
|
|
func NewOptions(name, secret string, secure bool, expiry time.Duration) *Options {
|
|
return &Options{name, secret, secure, expiry}
|
|
}
|
|
|
|
// Manager ...
|
|
type Manager struct {
|
|
options *Options
|
|
store Store
|
|
}
|
|
|
|
// NewManager ...
|
|
func NewManager(options *Options, store Store) *Manager {
|
|
return &Manager{options, store}
|
|
}
|
|
|
|
// Create ...
|
|
func (m *Manager) Create(w http.ResponseWriter) (*Session, error) {
|
|
sid, err := NewSessionID(m.options.secret)
|
|
if err != nil {
|
|
log.WithError(err).Error("error creating new session")
|
|
return nil, err
|
|
}
|
|
|
|
cookie := &http.Cookie{
|
|
Name: m.options.name,
|
|
Value: sid.String(),
|
|
Path: "/",
|
|
Secure: m.options.secure,
|
|
HttpOnly: true,
|
|
SameSite: http.SameSiteLaxMode,
|
|
MaxAge: int(m.options.expiry.Seconds()),
|
|
Expires: time.Now().Add(m.options.expiry),
|
|
}
|
|
|
|
securecookie.SetSecureCookie(w, m.options.secret, cookie)
|
|
|
|
return &Session{
|
|
store: m.store,
|
|
|
|
ID: sid.String(),
|
|
Data: make(Map),
|
|
CreatedAt: time.Now(),
|
|
ExpiresAt: time.Now().Add(m.options.expiry),
|
|
}, nil
|
|
}
|
|
|
|
// Validate ....
|
|
func (m *Manager) Validate(value string) (ID, error) {
|
|
sessionID, err := ValidateSessionID(value, m.options.secret)
|
|
return sessionID, err
|
|
}
|
|
|
|
// GetOrCreate ...
|
|
func (m *Manager) GetOrCreate(w http.ResponseWriter, r *http.Request) (*Session, error) {
|
|
cookie, err := securecookie.GetSecureCookie(
|
|
r,
|
|
m.options.secret,
|
|
m.options.name,
|
|
)
|
|
if err != nil {
|
|
sess, err := m.Create(w)
|
|
if err != nil {
|
|
log.WithError(err).Error("error creating new session")
|
|
return nil, err
|
|
}
|
|
if err = m.store.SetSession(sess.ID, sess); err != nil {
|
|
log.WithError(err).Errorf("error creating new session for %s", sess.ID)
|
|
return nil, err
|
|
}
|
|
return sess, nil
|
|
}
|
|
|
|
sid, err := m.Validate(cookie.Value)
|
|
if err != nil {
|
|
log.WithError(err).Error("error validating seesion")
|
|
return nil, err
|
|
}
|
|
|
|
sess, err := m.store.GetSession(sid.String())
|
|
if err != nil {
|
|
if err == ErrSessionNotFound {
|
|
log.WithError(err).Warnf("no session found for %s (creating new one)", sid)
|
|
m.Delete(w, r)
|
|
|
|
sess, err := m.Create(w)
|
|
if err != nil {
|
|
log.WithError(err).Error("error creating new session")
|
|
return nil, err
|
|
}
|
|
if err = m.store.SetSession(sess.ID, sess); err != nil {
|
|
log.WithError(err).Errorf("error creating new session for %s", sess.ID)
|
|
return nil, err
|
|
}
|
|
return sess, nil
|
|
}
|
|
log.WithError(err).Errorf("error loading session for %s", sid)
|
|
return nil, err
|
|
}
|
|
|
|
return sess, nil
|
|
}
|
|
|
|
// Delete ...
|
|
func (m *Manager) Delete(w http.ResponseWriter, r *http.Request) {
|
|
if sess := r.Context().Value(SessionKey); sess != nil {
|
|
sess := sess.(*Session)
|
|
if err := m.store.DelSession(sess.ID); err != nil {
|
|
log.WithError(err).Warnf("error deleting session %s", sess.ID)
|
|
}
|
|
}
|
|
|
|
cookie := &http.Cookie{
|
|
Name: m.options.name,
|
|
Value: "",
|
|
Secure: m.options.secure,
|
|
HttpOnly: true,
|
|
SameSite: http.SameSiteStrictMode,
|
|
MaxAge: -1,
|
|
Expires: time.Now(),
|
|
}
|
|
|
|
securecookie.SetSecureCookie(w, m.options.secret, cookie)
|
|
}
|
|
|
|
// Handler ...
|
|
func (m *Manager) Handler(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
sess, err := m.GetOrCreate(w, r)
|
|
if err != nil {
|
|
log.WithError(err).Error("session error")
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
ctx := context.WithValue(r.Context(), SessionKey, sess)
|
|
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
})
|
|
}
|