Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change CookieStore and its fields to be non-exported #19

Merged
merged 1 commit into from
Dec 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
35 changes: 11 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
# 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 a user `Session`
* 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 +30,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 +94,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}))
}