Skip to content

Commit

Permalink
Change CookieStore and its fields to be non-exported
Browse files Browse the repository at this point in the history
* Change NewCookieStore to require a `*CookieConfig` and return a Store
* Rename Config struct to CookieConfig
* Add DefaultCookieConfig and DebugCookieConfig exported variables
  • Loading branch information
dghubble committed Dec 31, 2022
1 parent df8e48f commit 555d474
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 34 deletions.
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ Notable changes between releases.

## Latest

* Change `CookieStore` and its fields to be non-exported ([#19](https://github.com/dghubble/sessions/pull/19))
* Change `NewCookieStore` to require a `*CookieConfig` and return a `Store`
* Rename `Config` struct to `CookieConfig`
* Add `DefaultCookieConfig` and `DebugCookieConfig`
* Change the `Session` field `Values` to be non-exported ([#18](https://github.com/dghubble/sessions/pull/18))
* Add `Session` `Set` method to set a key/value pair
* Add `Session` `Get` method to get a value for a given key
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ func NewServer() (http.Handler) {
...
// client-side cookies
sessionProvider := sessions.NewCookieStore(
sessions.DefaultCookieConfig,
// use a 32 byte or 64 byte hash key
[]byte("signing-secret"),
// use a 32 byte (AES-256) encryption key
[]byte("encryption-secret")
)
sessionProvider.Config.SameSite = http.SameSiteStrictMode
...
}
```
Expand Down
38 changes: 30 additions & 8 deletions cookie.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,48 @@ import (
"time"
)

// Config is the set of cookie properties.
type Config struct {
// cookie domain/path scope (leave zeroed for requested resource scope)
Path string
// DefaultCookieConfig configures http.Cookie creation for production.
var DefaultCookieConfig = &CookieConfig{
Path: "/",
MaxAge: defaultMaxAge,
HTTPOnly: true,
Secure: true,
SameSite: http.SameSiteStrictMode,
}

// DebugCookieConfig configures http.Cookie creation for debugging. It
// does NOT require cookies be sent over HTTPS so it should only be used
// in development. Prefer DefaultCookieConfig.
var DebugCookieConfig = &CookieConfig{
Path: "/",
MaxAge: defaultMaxAge,
HTTPOnly: true,
Secure: false,
SameSite: http.SameSiteLaxMode,
}

// CookieConfig configures http.Cookie creation.
type CookieConfig struct {
// Cookie domain/path scope (leave zeroed for requested resource scope)
// Defaults to the domain name of the responding server when unset
Domain string
// Defaults to the path of the responding URL when unset
Path string
// 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.
MaxAge int
// cookie may only be transferred over HTTPS
// cookie may only be transferred over HTTPS. Recommend true.
Secure bool
// browser should prohibit non-HTTP (i.e. javascript) cookie access
// browser should prohibit non-HTTP (i.e. javascript) cookie access. Recommend true
HTTPOnly bool
// prohibit sending in cross-site requests with SameSiteLaxMode or SameSiteLaxMode
// prohibit sending in cross-site requests with SameSiteLaxMode or SameSiteStrictMode
SameSite http.SameSite
}

// newCookie returns a new http.Cookie with the given name, value, and
// properties from config.
func newCookie(name, value string, config *Config) *http.Cookie {
func newCookie(name, value string, config *CookieConfig) *http.Cookie {
cookie := &http.Cookie{
Name: name,
Value: value,
Expand Down
47 changes: 22 additions & 25 deletions store.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import (
"github.com/gorilla/securecookie"
)

// Store is the interface for creating, reading, updating and destroying
// named Sessions.
// A Store manages creating, accessing, writing, and expiring Sessions.
type Store interface {
New(name string) *Session
Get(req *http.Request, name string) (*Session, error)
Expand All @@ -16,59 +15,57 @@ type Store interface {
}

// CookieStore stores Sessions in secure cookies (i.e. client-side)
type CookieStore struct {
type cookieStore struct {
config *CookieConfig
// encodes and decodes signed and optionally encrypted cookie values
Codecs []securecookie.Codec
// configures session cookie properties of new Sessions
Config *Config
codecs []securecookie.Codec
}

// NewCookieStore returns a new CookieStore which signs and optionally encrypts
// session cookies.
func NewCookieStore(keyPairs ...[]byte) *CookieStore {
return &CookieStore{
Codecs: securecookie.CodecsFromPairs(keyPairs...),
Config: &Config{
Path: "/",
MaxAge: defaultMaxAge,
HTTPOnly: true,
SameSite: http.SameSiteDefaultMode,
},
// NewCookieStore returns a new Store that signs and optionally encrypts
// session state in http cookies.
func NewCookieStore(config *CookieConfig, keyPairs ...[]byte) Store {
if config == nil {
config = DefaultCookieConfig
}

return &cookieStore{
config: config,
codecs: securecookie.CodecsFromPairs(keyPairs...),
}
}

// New returns a new Session with the requested name and the store's config
// value.
func (s *CookieStore) New(name string) *Session {
func (s *cookieStore) New(name string) *Session {
return NewSession(s, name)
}

// Get returns the named Session from the Request. Returns an error if the
// session cookie cannot be found, the cookie verification fails, or an error
// occurs decoding the cookie value.
func (s *CookieStore) Get(req *http.Request, name string) (session *Session, err error) {
func (s *cookieStore) Get(req *http.Request, name string) (session *Session, err error) {
cookie, err := req.Cookie(name)
if err == nil {
session = s.New(name)
err = securecookie.DecodeMulti(name, cookie.Value, &session.values, s.Codecs...)
err = securecookie.DecodeMulti(name, cookie.Value, &session.values, s.codecs...)
}
return session, err
}

// Save adds or updates the Session on the response via a signed and optionally
// encrypted session cookie. Session Values are encoded into the cookie value
// and the session Config sets cookie properties.
func (s *CookieStore) Save(w http.ResponseWriter, session *Session) error {
cookieValue, err := securecookie.EncodeMulti(session.Name(), &session.values, s.Codecs...)
func (s *cookieStore) Save(w http.ResponseWriter, session *Session) error {
cookieValue, err := securecookie.EncodeMulti(session.Name(), &session.values, s.codecs...)
if err != nil {
return err
}
http.SetCookie(w, newCookie(session.Name(), cookieValue, s.Config))
http.SetCookie(w, newCookie(session.Name(), cookieValue, s.config))
return nil
}

// Destroy deletes the Session with the given name by issuing an expired
// session cookie with the same name.
func (s *CookieStore) Destroy(w http.ResponseWriter, name string) {
http.SetCookie(w, newCookie(name, "", &Config{MaxAge: -1, Path: s.Config.Path}))
func (s *cookieStore) Destroy(w http.ResponseWriter, name string) {
http.SetCookie(w, newCookie(name, "", &CookieConfig{MaxAge: -1, Path: s.config.Path}))
}

0 comments on commit 555d474

Please sign in to comment.