Documentation
¶
Overview ¶
Package session provides server-side and cookie sessions with flash messages, old input, regeneration (anti-fixation) and idle/absolute timeouts. Sessions are loaded lazily and written only when modified.
A Manager built by New wraps handlers with Manager.Middleware; handlers reach the Session through web.Ctx.Session. Data lives in an AES-GCM encrypted cookie by default, or server-side in a Store: MemoryStore, SQLStore or drivers/redis. Apps get a manager from app.Sessions(), configured by SESSION_* variables.
See https://github.com/AlexGalhardo/vukem/blob/main/docs/http.md.
Example ¶
package main
import (
"fmt"
"log/slog"
"net/http"
"net/http/httptest"
"github.com/AlexGalhardo/vukem/session"
"github.com/AlexGalhardo/vukem/web"
)
func main() {
m, err := session.New(session.Config{
Store: session.NewMemoryStore(), // nil = encrypted cookie sessions (CookieKey)
Log: slog.New(slog.DiscardHandler),
})
if err != nil {
fmt.Println(err)
return
}
visits := m.Middleware()(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
s, _ := web.SessionFrom(r.Context()).(*session.Session)
n, _ := s.GetInt64("visits")
s.Put("visits", n+1)
fmt.Fprintf(w, "visit %d", n+1)
}))
var cookies []*http.Cookie
for range 2 {
req := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
for _, ck := range cookies {
req.AddCookie(ck)
}
rec := httptest.NewRecorder()
visits.ServeHTTP(rec, req)
cookies = rec.Result().Cookies()
fmt.Println(rec.Body.String())
}
}
Output: visit 1 visit 2
Index ¶
- Constants
- Variables
- func Migrations(table string) []schema.Migration
- type Config
- type Info
- type ListStore
- type Manager
- func (m *Manager) CookieName() string
- func (m *Manager) Cookies(ctx context.Context, data map[string]any) []*http.Cookie
- func (m *Manager) Middleware() func(http.Handler) http.Handler
- func (m *Manager) Peek(ctx context.Context, cookies []*http.Cookie) map[string]any
- func (m *Manager) RevokeOthers(ctx context.Context, userID, keepID string) error
- func (m *Manager) RevokeSession(ctx context.Context, userID, handle string) error
- func (m *Manager) UserSessions(ctx context.Context, userID, currentID string) ([]Info, error)
- type MemoryStore
- func (m *MemoryStore) Create(_ context.Context, id, userID string, data []byte, expiry time.Time) error
- func (m *MemoryStore) Delete(_ context.Context, id string) error
- func (m *MemoryStore) DeleteUserKey(_ context.Context, userID, key string) (bool, error)
- func (m *MemoryStore) Len() int
- func (m *MemoryStore) ListUser(_ context.Context, userID string) ([]StoredSession, error)
- func (m *MemoryStore) Load(_ context.Context, id string) ([]byte, bool, error)
- func (m *MemoryStore) RevokeOthers(_ context.Context, userID, keepID string) error
- func (m *MemoryStore) Save(_ context.Context, id string, data []byte, expiry time.Time) error
- func (m *MemoryStore) Update(_ context.Context, id, userID string, data []byte, expiry time.Time) error
- type SQLOption
- type SQLStore
- func (s *SQLStore) Create(ctx context.Context, id, userID string, data []byte, expiry time.Time) error
- func (s *SQLStore) CreateTable(ctx context.Context) error
- func (s *SQLStore) Delete(ctx context.Context, id string) error
- func (s *SQLStore) DeleteUserKey(ctx context.Context, userID, key string) (bool, error)
- func (s *SQLStore) ListUser(ctx context.Context, userID string) ([]StoredSession, error)
- func (s *SQLStore) Load(ctx context.Context, id string) ([]byte, bool, error)
- func (s *SQLStore) Prune(ctx context.Context) (int64, error)
- func (s *SQLStore) RevokeOthers(ctx context.Context, userID, keepID string) error
- func (s *SQLStore) Save(ctx context.Context, id string, data []byte, expiry time.Time) error
- func (s *SQLStore) Update(ctx context.Context, id, userID string, data []byte, expiry time.Time) error
- type Session
- func (s *Session) All() map[string]any
- func (s *Session) Flash(key string, value any)
- func (s *Session) Forget(keys ...string)
- func (s *Session) Get(key string) any
- func (s *Session) GetInt64(key string) (int64, bool)
- func (s *Session) GetString(key string) string
- func (s *Session) Has(key string) bool
- func (s *Session) ID() string
- func (s *Session) Invalidate()
- func (s *Session) Pull(key string) any
- func (s *Session) Put(key string, value any)
- func (s *Session) Reflash()
- func (s *Session) Regenerate()
- type Store
- type StoredSession
- type UserStore
Examples ¶
Constants ¶
const DefaultTable = "sessions"
DefaultTable is the table the SQL store owns when WithTable is not used. It is listed with every other table vukem owns in docs/database.md#tables-the-framework-owns.
const MigrationID = "20261018000001_vukem_create_sessions_table"
MigrationID is the id of the migration Migrations returns; it names the framework's own migration in vukem migrate:status.
Variables ¶
var ErrCookieTooLarge = errors.New("session: cookie session data exceeds 4 KB; use a server-side store")
ErrCookieTooLarge is returned when cookie session data exceeds 4 KB.
var ErrListUnsupported = errors.New(
"session: listing sessions needs a server-side ListStore (SESSION_DRIVER=database)",
)
ErrListUnsupported is returned by Manager.UserSessions when the sessions live in cookies or in a store that is not a ListStore.
var ErrRevokeUnsupported = errors.New(
"session: revoking sessions needs a server-side UserStore (SESSION_DRIVER=database)",
)
ErrRevokeUnsupported is returned by Manager.RevokeOthers when the sessions live in cookies or in a store that is not a UserStore: there is no server-side record to delete.
var ErrSessionNotFound = errors.New("session: session not found")
ErrSessionNotFound is returned by UserStore.Update when the session no longer exists.
Functions ¶
func Migrations ¶ added in v0.29.0
Migrations returns the migration that creates the sessions table (the default when table is empty), so an app can manage it with vukem migrate instead of leaving it to the boot-time CreateTable. The statements are the ones CreateTable runs, which stays as the fallback for apps that do not register it.
The DDL follows the dialect the migration runs on, so the same migration serves SQLite, PostgreSQL and MySQL. On a database where the table already exists (the store created it before the app opted in) the migration records itself and changes nothing; Down drops the table.
Types ¶
type Config ¶
type Config struct {
// CookieName defaults to "vukem_session"; with Secure, Path "/" and no
// Domain the "__Host-" prefix is added automatically.
CookieName string
// Path defaults to "/".
Path string
// Domain is empty (host-only cookie) by default.
Domain string
// Secure sends the cookie over HTTPS only. Required in production.
Secure bool
// SameSite defaults to Lax.
SameSite http.SameSite
// IdleTimeout ends sessions after inactivity (default 2h).
IdleTimeout time.Duration
// Lifetime ends sessions this long after creation (default 24h).
Lifetime time.Duration
// Store keeps sessions server-side; nil stores them in an encrypted
// cookie (CookieKey required).
Store Store
// CookieKey is the 32-byte AES-256-GCM key for cookie sessions; derive
// it from APP_KEY with the purpose vukem.New uses:
// app.DeriveKey("SESSION_DRIVER=cookie", 32).
CookieKey []byte
// PreviousCookieKeys still open cookie sessions sealed before an
// APP_KEY rotation (derive them from APP_PREVIOUS_KEYS with
// app.DerivePreviousKeys); such a session is re-sealed with CookieKey on
// that request. Each must be 32 bytes.
PreviousCookieKeys [][]byte
// Production enables boot-time checks (Secure must be true).
Production bool
// Now is the clock (tests); defaults to time.Now. When set, New hands
// it to the store so expiry and Prune follow the same clock: the
// built-in MemoryStore and SQLStore, and any store (or wrapper
// embedding one) with a setClock(func() time.Time) method.
Now func() time.Time
// Log receives security events and storage errors.
Log *slog.Logger
// RecordClient stores the User-Agent (capped at 256 bytes) and the
// client network (IPv4 /24, IPv6 /48) with each new logged-in session
// of a ListStore, for Manager.UserSessions. Off by default: both are
// personal data, so turn it on only with a reason to show them.
RecordClient bool
}
Config configures a Manager. Zero values use secure defaults.
type Info ¶ added in v0.25.0
type Info struct {
// Handle names the session for RevokeSession: the first 16 hex digits
// of SHA-256(storage key). It is safe to show and cannot load the
// session.
Handle string
// CreatedAt is when the session started (the login regenerates it).
CreatedAt time.Time
// LastSeenAt is the last request, to the minute (writes that only
// refresh the idle timeout happen at most once a minute).
LastSeenAt time.Time
// ExpiresAt is when the session ends unless it is used again.
ExpiresAt time.Time
// UserAgent and IP are recorded at login only with Config.RecordClient;
// IP keeps the network part only (IPv4 /24, IPv6 /48). UserAgent comes
// from the client: control and format runes are removed, but escape it
// when rendering (html/template does).
UserAgent, IP string
// Current is true for the session of the currentID given to
// UserSessions.
Current bool
}
Info describes one session of a user.
type ListStore ¶ added in v0.25.0
type ListStore interface {
UserStore
// ListUser returns the live sessions of userID.
ListUser(ctx context.Context, userID string) ([]StoredSession, error)
// DeleteUserKey deletes the session stored under key when it belongs
// to userID and reports whether one was deleted.
DeleteUserKey(ctx context.Context, userID, key string) (bool, error)
}
ListStore is a UserStore that lists the sessions of a user and deletes one of them by key, for an "active sessions" page. SQLStore, MemoryStore and drivers/redis implement it.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager loads and saves sessions around each request.
func New ¶
New validates cfg and returns a Manager. Insecure production settings are boot errors (docs/security.md, A02/A07).
func (*Manager) CookieName ¶
CookieName returns the effective cookie name.
func (*Manager) Cookies ¶ added in v0.5.0
Cookies returns the cookies of a new session holding data, as the middleware would set them. Tests use it to start a request already logged in (vukemtest Client.ActingAs).
func (*Manager) Middleware ¶
Middleware loads the session lazily, exposes it through web.Ctx (and web.SessionFrom), installs the flash-and-redirect validation hook, and saves it before the response headers are sent.
func (*Manager) Peek ¶
Peek decodes the session carried by cookies, as the next request would see it. Tests use it to assert on flash data and session values.
func (*Manager) RevokeOthers ¶ added in v0.24.0
RevokeOthers ends every session of userID except keepID (the current session id, or "" to end them all): call it after a password change or reset and when two-factor authentication is turned on. Sessions belong to the user stored under web.SessionUserKey, which auth guards set on login. It returns ErrRevokeUnsupported for cookie sessions and stores that are not a UserStore.
func (*Manager) RevokeSession ¶ added in v0.25.0
RevokeSession ends the session of userID named by handle (from UserSessions). It returns ErrSessionNotFound when handle names no session of that user, so one user cannot end another's sessions, and ErrRevokeUnsupported for cookie sessions and stores that are not a ListStore.
func (*Manager) UserSessions ¶ added in v0.25.0
UserSessions lists the live sessions of userID (the owner recorded under web.SessionUserKey, auth.SessionGuard.SessionUserID), most recently used first; currentID marks the caller's own session. It returns ErrListUnsupported for cookie sessions and stores that are not a ListStore.
type MemoryStore ¶
type MemoryStore struct {
// contains filtered or unexported fields
}
MemoryStore keeps sessions in process memory (development and tests).
func NewMemoryStore ¶
func NewMemoryStore() *MemoryStore
NewMemoryStore returns an empty MemoryStore.
func (*MemoryStore) Create ¶ added in v0.24.0
func (m *MemoryStore) Create(_ context.Context, id, userID string, data []byte, expiry time.Time) error
Create implements UserStore.
func (*MemoryStore) Delete ¶
func (m *MemoryStore) Delete(_ context.Context, id string) error
Delete implements Store.
func (*MemoryStore) DeleteUserKey ¶ added in v0.25.0
DeleteUserKey implements ListStore.
func (*MemoryStore) Len ¶
func (m *MemoryStore) Len() int
Len returns the number of stored sessions (tests).
func (*MemoryStore) ListUser ¶ added in v0.25.0
func (m *MemoryStore) ListUser(_ context.Context, userID string) ([]StoredSession, error)
ListUser implements ListStore.
func (*MemoryStore) RevokeOthers ¶ added in v0.24.0
func (m *MemoryStore) RevokeOthers(_ context.Context, userID, keepID string) error
RevokeOthers implements UserStore.
type SQLOption ¶
type SQLOption func(*SQLStore)
SQLOption configures NewSQLStore.
func WithDollarPlaceholders ¶
func WithDollarPlaceholders() SQLOption
WithDollarPlaceholders uses $1, $2... (PostgreSQL) instead of ?.
type SQLStore ¶
type SQLStore struct {
// contains filtered or unexported fields
}
SQLStore keeps sessions in a SQL table (SQLite, PostgreSQL or MySQL):
CREATE TABLE sessions (id TEXT PRIMARY KEY, data TEXT NOT NULL, expires_at BIGINT NOT NULL, user_id VARCHAR(255))
The id column holds hex(SHA-256(session id)), never the cookie value, so reading the table (backup, SQL injection elsewhere, replica access) does not yield session cookies. Rows written before ids were hashed are re-keyed on their first load and otherwise expire as usual.
func NewSQLStore ¶
NewSQLStore returns a SQLStore. The table name is validated because it is interpolated into SQL (A05).
func (*SQLStore) Create ¶ added in v0.24.0
func (s *SQLStore) Create(ctx context.Context, id, userID string, data []byte, expiry time.Time) error
Create implements UserStore.
func (*SQLStore) CreateTable ¶
CreateTable creates the sessions table if it does not exist, and adds the user_id column and its index to tables created by older versions. It is idempotent and safe to run from several servers at boot.
func (*SQLStore) DeleteUserKey ¶ added in v0.25.0
DeleteUserKey implements ListStore.
func (*SQLStore) ListUser ¶ added in v0.25.0
ListUser implements ListStore. Rows still keyed by a clear id (written before ids were hashed) are left out: their key is a session id.
func (*SQLStore) RevokeOthers ¶ added in v0.24.0
RevokeOthers implements UserStore. Sessions saved before user_id existed carry no user until their next save and are not matched.
type Session ¶
type Session struct {
// contains filtered or unexported fields
}
Session is one user's session. It is safe for concurrent use within a request.
func (*Session) ID ¶
ID returns the server-side session id: the store key, also listed by session devices and revocation. A new session has an id only once saved (after the response); Regenerate and Invalidate issue a new one at once. Cookie sessions (no Store) keep their data in the encrypted cookie and have no id: ID is always "" for them, Regenerate included.
func (*Session) Invalidate ¶
func (s *Session) Invalidate()
Invalidate clears all data and issues a new id (logout).
func (*Session) Reflash ¶
func (s *Session) Reflash()
Reflash keeps the current flash data for one more request.
func (*Session) Regenerate ¶
func (s *Session) Regenerate()
Regenerate issues a new id and keeps the data. Call it after login and any privilege change (session fixation, A07).
type Store ¶
type Store interface {
// Load returns the data saved for id; found is false when the session
// does not exist or expired.
Load(ctx context.Context, id string) (data []byte, found bool, err error)
// Save stores data for id until expiry.
Save(ctx context.Context, id string, data []byte, expiry time.Time) error
// Delete removes id.
Delete(ctx context.Context, id string) error
}
Store persists server-side sessions.
type StoredSession ¶ added in v0.25.0
type StoredSession struct {
// Key is hex(SHA-256(session id)), never the id itself.
Key string
// Data is the encoded session, as passed to Create and Update.
Data []byte
// Expiry is when the session ends unless it is used again.
Expiry time.Time
}
StoredSession is one session as a ListStore returns it.
type UserStore ¶ added in v0.24.0
type UserStore interface {
Store
// Create stores a new session owned by userID ("" for a guest).
Create(ctx context.Context, id, userID string, data []byte, expiry time.Time) error
// Update rewrites an existing session and returns ErrSessionNotFound
// when it is gone (revoked, logged out, pruned): a request that was in
// flight during a revocation must not write the session back.
Update(ctx context.Context, id, userID string, data []byte, expiry time.Time) error
// RevokeOthers deletes every session of userID except keepID ("" keeps
// none).
RevokeOthers(ctx context.Context, userID, keepID string) error
}
UserStore is a Store that records which user owns each session, so all sessions of a user can be ended at once (password reset, 2FA enable). SQLStore, MemoryStore and drivers/redis implement it; cookie sessions cannot.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package sessiontest is the conformance suite every session.ListStore passes (memory and database in core, Redis in drivers/redis).
|
Package sessiontest is the conformance suite every session.ListStore passes (memory and database in core, Redis in drivers/redis). |