qsess

package
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 11, 2021 License: MIT Imports: 13 Imported by: 2

Documentation

Overview

Package qsess implements web sessions, with a user-definable session data type, support for cookies and tokens, session revocation by user id, and back-ends for goleveldb, Cassandra/Syclla, PostgreSQL, MySQL, and a simple, in-memory store.

It is independent of, but integrates easily with, routers, middleware frameworks, http.Request.Context(), etc. It has zero dependencies beyond the standard library. (Database back-ends, which reside in sub-packages, depend on their respective database drivers, and any dependencies those drivers require.)

var qsStore *qsess.Store

func main() {
	...
	qsStore, err = qsess.NewMapStore([]byte("encryption-key------------------"))
	...
}

func loginHandler(w http.ResponseWriter, r *http.Request) {
	...
	// authenticate
	s := qsStore.NewSession(userID)
	// fill in session data
	err := s.Save(w)
	...
}

func dosomethingHandler(w http.ResponseWriter, r *http.Request) {
	sess, ttl, err := qsStore.GetSession(w, r)
	...
	// if session data has been modified, or it's time to refresh
		sess.Save(w)
	...
}

func logoutHandler(w http.ResponseWriter, r *http.Request) {
	sess, _, err := qsStore.GetSession(w, r)
	...
	err = sess.Delete(w)
	...
}

Session data is persisted in the server and accessed via Session.Data. (Storing session data only in clients, with a "stateless" back-end, is not supported.)

If the only session data you require is a user id, you can ignore Session.Data entirely. Just provide the user id as a byte slice to Store.NewSession and get it via Session.UserID. Set Store.NewSessData to nil, to avoid allocating an empty VarMap for every session.

If you require session data beyond just a user id, it is recommended that you supply a data type and serializer, using Store.NewSessData. (See qstest/benchmark_test.go for examples.) If you do not, the default session data type, VarMap, is a map[interface{}]interface{} with gob serialization (which is very slow, compared to the alternatives in qstest/benchmark_test.go).

If AuthType is TokenAuth, session references are transmitted to/from the client as tokens, rather than cookies. Tokens are opaque, base64-encoded strings, but they can be wrapped in structured formats, like JWT, by user code. To send tokens to clients, user code must either set Store.SendToken and Store.DeleteToken or call Session.Token to obtain tokens and manage token communication explicitly. To receive tokens from clients, GetSession, by default, reads tokens from request headers of the form, "Authorization: Bearer <token>". This can be overridden by supplying a GetToken callback.

By default, cookies and tokens are encrypted and authenticated, using AES-GCM. This can be overridden by supplying Encrypt and Decrypt functions.

Multiple Stores can be used simultaneously. For example, one Store can be used to implement login sessions via cookies, while another is used to generate and track sign-up email verification tokens.

Sessions are automatically deleted if not Saved within their expiration times. This package does not refresh sessions (i.e. reset their expiration times), except for the implicit refresh that happens whenever Save is called. User code learns the remaining time-to-live whenever it calls GetSession, and it can perform a refresh simply by calling Save. (See MwRequireSess in package qctx for an example of this.)

When a user changes a password, you can revoke all active sessions for the user by calling DeleteByUserID. To use this optional capability, you must supply a user id each time you create a session. User ids are application-defined and are not interpreted or modified by session code. They are persisted to the database and available for the life of the session, by calling UserID.

Index

Constants

View Source
const (
	DefaultAuthType       = CookieAuth
	DefaultMaxAgeSecs     = 24 * 60 * 60
	DefaultMinRefreshSecs = 1 * 60 * 60
	DefaultCookieName     = "qsess_session"
	DefaultCookieDomain   = ""
	DefaultCookiePath     = "/"
	DefaultCookieSecure   = false
	DefaultCookieHTTPOnly = true
	DefaultCookieSameSite = http.SameSiteDefaultMode
)

Variables

This section is empty.

Functions

This section is empty.

Types

type AuthTypeEnum

type AuthTypeEnum int

AuthTypeEnum specifies how references to sessions are stored in clients - in cookies or in tokens.

const (
	CookieAuth AuthTypeEnum = iota
	TokenAuth
)

func ParseAuthType

func ParseAuthType(s string) (AuthTypeEnum, error)

func (AuthTypeEnum) String

func (a AuthTypeEnum) String() string

type SessBackEnd

type SessBackEnd interface {
	// Save might have to create and save a new id, so id is passed by reference.
	Save(sessID *[]byte, data []byte, userID []byte, maxAgeSecs int, minRefreshSecs int) error
	// Get and Delete take a userID argument, but it is ignored except in
	// the rare case of a back-end that requires uidToClient = true.
	Get(sessID []byte, uID []byte) (data []byte, userID []byte, timeToLiveSecs int, maxAgeSecs int, minRefreshSecs int, err error)
	Delete(sessID []byte, uID []byte) error
	DeleteByUserID(userID []byte) error
}

A back-end consists of an implementation of SessBackEnd and a Store constructor. Session ids are back-end-defined and generated by SessBackEnd.Save, are not interpreted or modified by code outside of the back-end, and are not visible to users. User ids are application-defined. Back-ends track them for DeleteByUserId.

type SessData

type SessData interface {
	Marshal() ([]byte, error)
	Unmarshal([]byte) error
}

SessData is an interface for per-session data storage. The default session data type is VarMap. It can be replaced with a custom data type by setting Store.NewSessData. See qstest/benchmark_test.go for examples.

type Session

type Session struct {
	Data SessData
	// MaxAgeSecs and MinRefreshSecs are initialized to the corresponding
	// values in the Store. User code may set them to different values,
	// for example, to implement a "keep me logged in" option.
	// See Store for more information on how these values are used.
	MaxAgeSecs     int
	MinRefreshSecs int
	// contains filtered or unexported fields
}

func (*Session) Delete

func (s *Session) Delete(w http.ResponseWriter) error

Delete deletes a session, by deleting its database record. If you're using cookies, the corresponding cookie is also deleted. If you're using tokens, and you've registered an implementation of DeleteToken, Delete will call it to delete the token from the cient.

func (*Session) DeleteByUserID

func (s *Session) DeleteByUserID(w http.ResponseWriter) error

DeleteByUserID deletes all sessions for the given session's user id. If you are going to use DeleteByUserID, you must supply non-empty userIDs to NewSession.

It's OK to invoke DeleteByUserID on a newly-created session object, on which Save has never been called.

func (*Session) Save

func (s *Session) Save(w http.ResponseWriter) error

Save writes a session's data to the database and refreshes its expiration time.

If AuthType is CookieAuth, Save writes a cookie containing the session id into the response headers. A call to Save must precede any calls to ResponseWriter.Write or ResponseWriter.WriteHeader, otherwise, the cookie will not make it to the client.

If AuthType is TokenAuth, you must either supply an implementation of SendToken or make other arrangements for sending the token to the client.

func (*Session) Token

func (s *Session) Token() (token string, timeToLiveSecs int, err error)

Token returns a token referring to the current session, ready to be given to the client.

func (*Session) UserID

func (s *Session) UserID() []byte

UserID returns the user id that was specified when the session was created. Callers should NOT modify the contents of the returned byte slice.

type Store

type Store struct {
	// To use a custom session data type, register a constructor for an
	// implementation of SessData here.
	// NewSessData can also be set to nil, if the only session data you need
	// is a user id (which can be managed via NewSession and UserID).
	NewSessData func() SessData

	// SessMaxAgeSecs is the session expiration period. It must be positive.
	// It can be overridden in individual sessions by setting Session.MaxAgeSecs.
	MaxAgeSecs int

	// SessMinRefreshSecs enables applications to reduce refresh overhead,
	// by not automatically refreshing the session expiration time at
	// every request. Package qsess does not perform session refresh itself;
	// it maintains this value for use by applications and/or middleware.
	// For a usage example, see qctx.MwRequireSess.
	// It can be overridden in individual sessions via Session.MinRefreshSecs.
	// By convention, if MinRefreshSecs is negative, no refresh should be performed.
	MinRefreshSecs int

	// parameters for cookie creation
	CookieName     string
	CookieDomain   string
	CookiePath     string
	CookieSecure   bool
	CookieHTTPOnly bool
	CookieSameSite http.SameSite

	// AuthType specifies how sessions are stored in the client (cookies or tokens).
	AuthType AuthTypeEnum

	// callbacks for sending/receiving tokens to/from the client.
	SendToken   func(token string, timeToLiveSecs int, w http.ResponseWriter) error
	DeleteToken func(w http.ResponseWriter) error
	GetToken    func(w http.ResponseWriter, r *http.Request) (token string, err error)

	// Bring-your-own-crypto by registering Encrypt and Decrypt functions.
	Encrypt func(data []byte) ([]byte, error)
	Decrypt func(data []byte) ([]byte, error)

	// SessionSaved is an optional callback, which enables you to keep track
	// of users' last-visited time. This has no effect on session expiration;
	// it is purely for use by application code. You must supply a userID
	// with NewSession, for this to be useful.
	SessionSaved func(UserID []byte, timestamp time.Time) error

	// for back-ends that create a goroutine to prune expired sessions
	PruneInterval chan int // value is interval in seconds
	PruneKill     chan int // kill pruner goroutine, value doesn't matter
	// contains filtered or unexported fields
}

func NewMapStore

func NewMapStore(cipherkeys ...[]byte) (*Store, error)

NewMapStore creates a new session store, using a simple, in-memory map, with no persistence.

cipherkeys are one or more 32-byte encryption keys, to be used with AES-GCM. For encryption, only the first key is used; for decryption all keys are tried (allowing key rotation).

Additional configuration options can be set by manipulating fields in the returned qsess.Store.

func NewStore

func NewStore(backend SessBackEnd, uidToClient bool, cipherkeys ...[]byte) (*Store, error)

NewStore is exported only for use by back-ends. Users should never call NewStore; instead, they should call back-end-specific Store constructors.

func (*Store) BackEnd

func (st *Store) BackEnd() SessBackEnd

BackEnd is exported only for use by tests.

func (*Store) GetSession

func (st *Store) GetSession(w http.ResponseWriter, r *http.Request) (s *Session, timeToLiveSecs int, e error)

GetSession determines if the current HTTP request headers contain a cookie or token for an active session and, if so, returns a valid *Session, otherwise it returns a non-nil error.

func (*Store) GetTokenSession

func (st *Store) GetTokenSession(token string) (s *Session, timeToLiveSecs int, e error)

GetTokenSession determines if the given token refers to an active session and, if so, returns a valid *Session, otherwise it returns a non-nil error.

func (*Store) NewSession

func (st *Store) NewSession(userID []byte) *Session

NewSession creates a new session object. It is not persisted to until Session.Save() is called.

userID is an application-defined user id. If you are using DeleteByUserId, you must supply a nonempty userID. Even if you are not using DeleteByUserId, you may supply a userID, and it will be persisted by the back-end for the life of the session and will be accessible by calling UserID. Otherwise, userID can be empty or nil.

type VarMap

type VarMap struct {
	Vars map[interface{}]interface{}
}

VarMap is the default session data type.

func (*VarMap) Marshal

func (m *VarMap) Marshal() ([]byte, error)

func (*VarMap) Unmarshal

func (m *VarMap) Unmarshal(b []byte) error

Directories

Path Synopsis
Package qscql is a Cassandra/Scylla back-end for qsess.
Package qscql is a Cassandra/Scylla back-end for qsess.
Package qsldb is a goleveldb back-end for qsess.
Package qsldb is a goleveldb back-end for qsess.
Package qsmy is a MySQL back-end for qsess.
Package qsmy is a MySQL back-end for qsess.
Package qspgx is a back-end for qsess which uses PostgreSQL, accessed via the pgx package.
Package qspgx is a back-end for qsess which uses PostgreSQL, accessed via the pgx package.
Package qstest contains shared test code for use by qsess back-ends.
Package qstest contains shared test code for use by qsess back-ends.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL