Documentation
¶
Overview ¶
Package sessiondata keeps per-session app data in a table of its own, keyed by session id, for an app that wants the data out of the session record: a value too big for a cookie, a value the session store (Redis, memory) should not hold, or a table the app joins and reports on.
It is the second of the two shapes the owner decided on (2026-10-03). The first is Session.Data in the core: the same JSON, carried by every store inside the session, read with no query (auth.SessionData). This plugin offers the same typed API — Get and Set on the request's session — over a row per session, and keeps the rows in step with the sessions through the core's SessionLifecycle: a sign-out, a revoke, "sign out everywhere" and a deleted user remove the row, a second-factor promotion moves it to the new session, and Sweep removes the rows of sessions that reached their hard stop. There is no foreign key and no cascade (ADR-0002).
What it costs: Get is one query per call, where Session.Data is none. What it gives: the data never travels in a token or a cache entry, the bound is the same (auth.MaxSessionDataSize) but the row is in SQL whatever the session store, and the table is the app's to read.
Index ¶
- Constants
- Variables
- func Get[T any](ctx context.Context, p *Plugin) (v T, ok bool, err error)
- func GetFor[T any](ctx context.Context, p *Plugin, sessionID string) (v T, ok bool, err error)
- type Initial
- type Option
- type Plugin
- func (p *Plugin) DataHooks() auth.DataHooks
- func (p *Plugin) Init(a *auth.Auth) error
- func (p *Plugin) Name() string
- func (p *Plugin) OptionErr() error
- func (p *Plugin) SessionEnded(ctx context.Context, userID, id string) error
- func (p *Plugin) SessionReplaced(ctx context.Context, old, new auth.Session) error
- func (p *Plugin) Set(ctx context.Context, v any) error
- func (p *Plugin) SetFor(ctx context.Context, s auth.Session, v any) error
- func (p *Plugin) Sweep(ctx context.Context, cutoff time.Time) (int64, error)
- func (p *Plugin) Tables() []sqlb.Table
- func (p *Plugin) TypedMigrations(engine string, t auth.SQLTypes) ([]auth.Migration, error)
- func (p *Plugin) UserSessionsEnded(ctx context.Context, userID, exceptID string) error
- func (p *Plugin) UserTables() []auth.UserTable
Constants ¶
const ( //ds:def id=tablenamesessiondata-5kq8mw2x owner=@khanakia stability=stable TableNameData = "auth_session_data" TableAliasData = "sd" // ColSessionID is the session the row belongs to, and the primary key: // a session has one row, replaced on every Set. ColSessionID = "session_id" // ColUserID is the session's user, so "sign out everywhere" and // DeleteUser remove a user's rows without listing their sessions // (auth.UserTable). ColUserID = "user_id" // ColData is the app's JSON, the engine's document type. ColData = "data" // ColExpiresAt is the session's hard stop (Session.MaxExpiresAt, or its // expiry when it has none), after which Sweep removes the row. A // session that ended idle before its hard stop leaves its row until // then: the core sees no "end" for an expiry, in any store. ColExpiresAt = "expires_at" // ColUpdatedAt is when the data was last written. ColUpdatedAt = "updated_at" )
The data table: one row per session that has data.
const PluginName = "sessiondata"
PluginName is the plugin's permanent identity: migration owner and table owner.
Variables ¶
var ( TableData = sqlb.Table{Name: TableNameData, Alias: TableAliasData} DataSessionID = sqlb.Col[string](TableData, ColSessionID) DataUserID = sqlb.Col[string](TableData, ColUserID) DataValue = sqlb.Col[auth.AppData](TableData, ColData) DataExpiresAt = sqlb.Col[time.Time](TableData, ColExpiresAt) DataUpdatedAt = sqlb.Col[time.Time](TableData, ColUpdatedAt) )
Typed columns of the data table, for an app that reads or joins it.
Functions ¶
Types ¶
type Initial ¶
Initial computes the data a new session starts with, from the session as it will be stored; nil records nothing. It runs inside the transaction that creates the session, after the row exists, and an error fails the sign-in.
type Option ¶
type Option func(*Plugin)
Option configures the plugin; options are applied in order by New.
func WithInitial ¶
WithInitial records fn's value for every session the core creates: the workspace the user was last in, the device the sign-in came from. A pending second-factor session gets its row at the first factor, and the promotion moves it to the full session, so fn runs once per sign-in.
type Plugin ¶
type Plugin struct {
// contains filtered or unexported fields
}
Plugin keeps per-session app data in its own table. Build it with New.
func (*Plugin) DataHooks ¶
DataHooks record the initial data, when WithInitial was given, once the session row exists. The full session a PendingSecondFactor operation creates at a promotion is skipped: the pending session's row moves to it (SessionReplaced), so the value computed at the first factor is kept and the function runs once per sign-in.
func (*Plugin) OptionErr ¶
OptionErr is every error the options recorded, such as auth.ErrNilOption naming an option given nil; auth.New refuses to start while it is not nil (auth.OptionChecker).
func (*Plugin) SessionEnded ¶
SessionEnded deletes the session's row. The user id is in the WHERE, as in the store's Delete, so an id of another user's session matches nothing.
func (*Plugin) SessionReplaced ¶
SessionReplaced moves the pending session's row to the full session, with the full session's hard stop, so what was recorded at the first factor is there after the second. A row the full session already has (an app hook of its own wrote one) gives way to it: the pending row is the sign-in's state so far. No pending row, nothing to move.
func (*Plugin) Set ¶
Set replaces the data of the request's session in ctx with v, encoded as JSON; nil deletes the row. It refuses a signed-out request with auth.ErrSignedOut, an API-key request with auth.ErrSessionDataUnavailable and a value over auth.MaxSessionDataSize with auth.ErrSessionDataTooLarge. Of two concurrent writes the last wins.
func (*Plugin) SetFor ¶
SetFor is Set for any session: one from a list, or the one a hook is handed. The row's expiry is the session's hard stop.
func (*Plugin) Sweep ¶
Sweep deletes the rows of sessions whose hard stop is before cutoff, for auth sweep.
func (*Plugin) Tables ¶
Tables declares the data table, so New can check its alias and an app can rename it.
func (*Plugin) TypedMigrations ¶
TypedMigrations creates the table for engine, and refuses an engine the core cannot write for. MySQL has no CREATE INDEX IF NOT EXISTS, so there the index is declared inside the CREATE TABLE.
func (*Plugin) UserSessionsEnded ¶
UserSessionsEnded deletes every row of the user but the excepted session's.
func (*Plugin) UserTables ¶
UserTables tells DeleteUser which rows are a user's: every row of the table, by user id.