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 2b0844c
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 53 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` convenience variables
* 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
29 changes: 11 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
# sessions [![GoDoc](https://pkg.go.dev/badge/github.com/dghubble/sessions.svg)](https://pkg.go.dev/github.com/dghubble/sessions) [![Workflow](https://github.com/dghubble/sessions/actions/workflows/test.yaml/badge.svg)](https://github.com/dghubble/sessions/actions/workflows/test.yaml?query=branch%3Amain) [![Sponsors](https://img.shields.io/github/sponsors/dghubble?logo=github)](https://github.com/sponsors/dghubble) [![Mastodon](https://img.shields.io/badge/follow-news-6364ff?logo=mastodon)](https://fosstodon.org/@typhoon)
# sessions
[![GoDoc](https://pkg.go.dev/badge/github.com/dghubble/sessions.svg)](https://pkg.go.dev/github.com/dghubble/sessions)
[![Workflow](https://github.com/dghubble/sessions/actions/workflows/test.yaml/badge.svg)](https://github.com/dghubble/sessions/actions/workflows/test.yaml?query=branch%3Amain)
[![Sponsors](https://img.shields.io/github/sponsors/dghubble?logo=github)](https://github.com/sponsors/dghubble)
[![Mastodon](https://img.shields.io/badge/follow-news-6364ff?logo=mastodon)](https://fosstodon.org/@dghubble)

Package `sessions` provides minimalist Go sessions, backed by `securecookie` or database stores.

### Features

* `Store` provides an interface for managing sessions.
* `New` returns a new named `Session`.
* `Get` returns the named `Session` from the `http.Request` iff it was correctly verified and decoded. Otherwise the error is non-nil.
* `Save` encodes and signs Session.Value data.
* `Destroy` removes (expires) the session cookie of a given name.
* Each `Session` provides `Save` and `Destroy` convenience methods.
* Provides `CookieStore` for managing client-side secure cookies.
* Extensible for custom session database backends.
* `Store` provides an interface for managing user `Session`'s
* May be implemented by custom session database backends
* `Session` provides convenient key/value `Set`, `Get`, and `GetOk` methods
* `NewCookeiStore` implements a `Store` backed by client-side cookies (signed and optionally encrypted)

## Install

```
go get github.com/dghubble/sessions
```

## Documentation
## Docs

Read [GoDoc](https://godoc.org/github.com/dghubble/sessions)

Expand All @@ -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 Expand Up @@ -100,13 +100,6 @@ func (s server) Logout() http.Handler {
}
```

### Differences from gorilla/sessions

* Gorilla stores a context map of Requests to Sessions to abstract multiple sessions. `dghubble/sessions` provides individual sessions, leaving multiple sessions to a `multisessions` package. No Registry is needed.
* Gorilla has a depedency on `gorilla/context`, a non-standard context.
* Gorilla requires all handlers be wrapped in `context.ClearHandler` to avoid memory leaks.
* Gorilla's `Store` interface is surprising. `New` and `Get` can both possibly return a new session, a field check is needed. Some use cases expect developers to [ignore an error](https://github.com/gorilla/sessions/blob/master/doc.go#L32). `Destroy` isn't provided.

## License

[MIT License](LICENSE)
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
54 changes: 27 additions & 27 deletions store.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,69 +6,69 @@ 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 returns a new named Session
New(name string) *Session
// Get a named Session from the request
Get(req *http.Request, name string) (*Session, error)
// Save writes a Session to the ResponseWriter
Save(w http.ResponseWriter, session *Session) error
// Destroy removes (expires) a named Session
Destroy(w http.ResponseWriter, name string)
}

// 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 {
// New returns a new named 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 2b0844c

Please sign in to comment.