package session import ( "context" "errors" "net/http" "time" "go.neonxp.ru/mux" "go.neonxp.ru/objectid" ) type Config struct { SessionCookie string Path string Domain string Secure bool HttpOnly bool MaxAge time.Duration } var DefaultConfig Config = Config{ SessionCookie: "_session", Path: "/", Domain: "", Secure: false, HttpOnly: true, MaxAge: 365 * 24 * time.Hour, } var ( ErrSessionNotFound = errors.New("session not found") ErrNoSessionInContext = errors.New("no session in context") ) type SessionManager struct { config *Config storer Store } func New(storer Store) *SessionManager { return NewWithConfig(&DefaultConfig, storer) } func NewWithConfig(config *Config, storer Store) *SessionManager { return &SessionManager{ config: config, storer: storer, } } func (s *SessionManager) Middleware() mux.Middleware { return func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var ( sessionID string values Values ) cookie, err := r.Cookie(s.config.SessionCookie) switch { case err == nil: sessionID = cookie.Value values = s.storer.Load(sessionID) case errors.Is(err, http.ErrNoCookie): sessionID = objectid.New().String() } ctx := context.WithValue(r.Context(), sessionManagerKey, s) ctx = context.WithValue(ctx, sessionIDKey, sessionID) ctx = context.WithValue(ctx, sessionValueKey, values) h.ServeHTTP(w, r.WithContext(ctx)) }) } } func (s *SessionManager) Values(ctx context.Context) Values { aValue := ctx.Value(sessionValueKey) values, ok := aValue.(Values) if !ok || values == nil { values = Values{} } return values } func (s *SessionManager) Save(w http.ResponseWriter, r *http.Request, values Values) error { aSessionID := r.Context().Value(sessionIDKey) sessionID, ok := aSessionID.(string) if !ok { return ErrNoSessionInContext } http.SetCookie(w, &http.Cookie{ Name: s.config.SessionCookie, Value: sessionID, Path: s.config.Path, Domain: s.config.Domain, Secure: s.config.Secure, HttpOnly: s.config.HttpOnly, MaxAge: int(s.config.MaxAge.Seconds()), }) return s.storer.Save(sessionID, values) } func (s *SessionManager) Clear(w http.ResponseWriter, r *http.Request) error { aSessionID := r.Context().Value(sessionIDKey) sessionID, ok := aSessionID.(string) if !ok { return ErrNoSessionInContext } http.SetCookie(w, &http.Cookie{ Name: s.config.SessionCookie, Value: sessionID, Path: s.config.Path, Domain: s.config.Domain, Secure: s.config.Secure, HttpOnly: s.config.HttpOnly, MaxAge: -1, }) return s.storer.Remove(sessionID) } func FromRequest(r *http.Request) *SessionManager { return r.Context().Value(sessionManagerKey).(*SessionManager) }