authstate

package
v0.5.6 Latest Latest
Warning

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

Go to latest
Published: Sep 5, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

README

authstate

authstate provides the application-owned storage boundary for browser authentication correlation. Store.Take removes a value atomically before a successful return, so replaying a state key cannot re-enter a ceremony.

The package contains the shared Store[T] and Codec[T] contracts, the stable errors, and SQLStore[T], which runs on whatever engine a registered dialect describes. The engines and the other backends live in subpackages, and the import of one is what puts it in a binary:

  • authstate/sqlite, authstate/postgres, and authstate/mysql for the SQL engines
  • authstate/redis for Redis and Valkey
  • authstate/memory for bounded process-local storage
import _ "github.com/shibukawa/popcornweb/authstate/postgres"

store, err := authstate.NewSQLStore[oauth.Transaction](db, oauth.TransactionCodec{},
	authstate.SQLOptions{Dialect: "postgres", Namespace: "auth-oidc"})

The dialect is the name the DSN scheme already resolved to, so a deployment names its engine once. Take is one statement where an engine has RETURNING and a locking transaction where it does not, which is why the engines are whole implementations of four operations rather than a table of SQL fragments. TestEngineContract runs one suite against all three.

Multi-process deployments must use a store whose Take operation is atomic across processes. Durable adapters use explicit Codec[T] implementations; oauth.TransactionCodec and passkey.CeremonyStateCodec preserve the private correlation records used by those packages. Values containing challenge, state, nonce, or verifier material must never be logged.

Documentation

Overview

Package authstate provides expiring, single-use state storage for browser authentication flows.

Index

Constants

View Source
const TableName = "popcornweb_authstate"

TableName is the table the SQL stores own.

Variables

View Source
var (
	ErrNotFound       = errors.New("authstate: state not found")
	ErrExpired        = errors.New("authstate: state expired")
	ErrAlreadyExists  = errors.New("authstate: state already exists")
	ErrLimitExceeded  = errors.New("authstate: store limit exceeded")
	ErrInvalidKey     = errors.New("authstate: invalid key")
	ErrInvalidExpiry  = errors.New("authstate: invalid expiry")
	ErrInvalidOptions = errors.New("authstate: invalid options")
	ErrCodec          = errors.New("authstate: codec failure")
	ErrUnavailable    = errors.New("authstate: backend unavailable")
)
View Source
var Columns = []string{"namespace", "key", "expires_at_ms", "payload"}

Columns are the columns of that table, in the order every dialect declares them.

Functions

func Dialects

func Dialects() []string

Dialects lists the registered dialect names in order.

func Register

func Register(dialect Dialect)

Register adds an engine's dialect. An engine package calls it from init, so a blank import is what makes the SQL store work against that engine:

import _ "github.com/shibukawa/popcornweb/authstate/postgres"

A duplicate or incomplete dialect panics: two descriptions of one engine is a build mistake, not a runtime condition.

func ScanColumns

func ScanColumns(rows *sql.Rows) ([]string, error)

ScanColumns reads one column name per row, which is the shape every engine's catalog query is written to return.

func SchemaSQL

func SchemaSQL(dialect string) (string, error)

SchemaSQL returns the deterministic DDL of the owned table under one engine, so a project can carry it in a migration instead of creating it at startup.

Types

type Codec

type Codec[T any] interface {
	Encode(value T) ([]byte, error)
	Decode(encoded []byte) (T, error)
}

Codec explicitly serializes values stored by durable Store implementations. Implementations must use a versioned, bounded format and must not include value contents in returned errors.

type Dialect

type Dialect struct {
	// Name is the dialect identifier rule:rdb-dsn-resolution resolves a DSN
	// scheme to.
	Name string
	// CreateTable is the deterministic DDL of the owned table.
	CreateTable func() string
	// Insert stores one record unless a live one already holds the key, and
	// reports whether it stored.
	Insert func(ctx context.Context, db *sql.DB, record SQLRecord) (bool, error)
	// Take removes one record and returns what it held. A missing record is
	// sql.ErrNoRows, which the store reports as ErrNotFound.
	Take func(ctx context.Context, db *sql.DB, namespace, key string) (expiresAtMS int64, payload []byte, err error)
	// Prune removes at most limit records of one namespace that expired
	// before the given instant.
	Prune func(ctx context.Context, db *sql.DB, namespace string, beforeMS int64, limit int) (int64, error)
	// Columns lists the columns of the owned table in declaration order, or
	// none at all when the table does not exist.
	Columns func(ctx context.Context, db *sql.DB) ([]string, error)
}

Dialect is one engine's implementation of the four operations a SQL-backed store performs. These are whole operations rather than statement fragments because the engines differ in more than syntax: MySQL has no RETURNING, so its single-use read is a transaction where the others are one statement.

func DialectFor

func DialectFor(name string) (Dialect, error)

DialectFor resolves one engine, explaining the missing import rather than the missing map entry.

type RawCodec

type RawCodec struct{}

RawCodec passes payloads through unchanged, so a typed store can serve as a RawStore for a caller that owns the encoding itself.

func (RawCodec) Decode

func (RawCodec) Decode(encoded []byte) ([]byte, error)

func (RawCodec) Encode

func (RawCodec) Encode(value []byte) ([]byte, error)

type RawStore

type RawStore interface {
	Put(ctx context.Context, key string, payload []byte, expiresAt time.Time) error
	Take(ctx context.Context, key string) ([]byte, error)
}

RawStore is Store over already encoded payloads.

It exists so a storage backend can open a ceremony store for a value type it cannot name. plugin/auth keeps three kinds of ceremony record, two of whose types are unexported, so a backend package could never construct a Store[T] for them; it supplies this instead and the host adds the codec back with Typed.

The guarantees are the ones Store states: Take removes a value before returning it, an expired value is removed and never returned, and a stable error reveals nothing about what was stored.

type SQLOptions

type SQLOptions struct {
	// Dialect is the registered engine name, which is the dialect the DSN
	// scheme already resolved to.
	Dialect       string
	Namespace     string
	Now           func() time.Time
	MaxKeyBytes   int
	MaxValueBytes int
	MaxPruneBatch int
}

SQLOptions controls record isolation, resource bounds, and expiry behavior, and names the engine the statements run against.

type SQLRecord

type SQLRecord struct {
	Namespace   string
	Key         string
	ExpiresAtMS int64
	NowMS       int64
	Payload     []byte
}

SQLRecord is one row an Insert writes. NowMS decides whether an existing row is stale enough to replace: a live record is never overwritten, which is what makes a ceremony key single use.

type SQLStore

type SQLStore[T any] struct {
	// contains filtered or unexported fields
}

SQLStore persists expiring, single-use authentication state in a database/sql database. The engine is supplied by a registered Dialect, so this type carries no SQL of its own beyond what every engine shares.

func NewSQLRawStore

func NewSQLRawStore(db *sql.DB, options SQLOptions) (*SQLStore[[]byte], error)

NewSQLRawStore constructs a SQL store over already encoded payloads, which is the form a storage backend supplies for a value type it cannot name.

func NewSQLStore

func NewSQLStore[T any](db *sql.DB, codec Codec[T], options SQLOptions) (*SQLStore[T], error)

NewSQLStore constructs a store over db under the named engine. The caller retains ownership of db and must carry the migration, or call EnsureSchema, before serving requests.

func (*SQLStore[T]) EnsureSchema

func (s *SQLStore[T]) EnsureSchema(ctx context.Context) error

EnsureSchema creates the owned table when it is missing and verifies its column layout, which is what a project carrying the migration relies on at startup.

func (*SQLStore[T]) Prune

func (s *SQLStore[T]) Prune(ctx context.Context, before time.Time, limit int) (int64, error)

Prune removes at most limit expired records from this Store's namespace.

func (*SQLStore[T]) Put

func (s *SQLStore[T]) Put(ctx context.Context, key string, value T, expiresAt time.Time) error

func (*SQLStore[T]) Take

func (s *SQLStore[T]) Take(ctx context.Context, key string) (T, error)

type Store

type Store[T any] interface {
	Put(ctx context.Context, key string, value T, expiresAt time.Time) error
	Take(ctx context.Context, key string) (T, error)
}

Store atomically persists and consumes expiring authentication state. Implementations must remove a value before a successful Take returns.

func Typed

func Typed[T any](raw RawStore, codec Codec[T]) Store[T]

Typed puts the codec back on a RawStore, producing the contract callers use.

Directories

Path Synopsis
Package dynamo stores single-use authentication ceremony state in DynamoDB.
Package dynamo stores single-use authentication ceremony state in DynamoDB.
Package firestore stores single-use authentication ceremony state in Firestore in Datastore mode.
Package firestore stores single-use authentication ceremony state in Firestore in Datastore mode.
Package memory provides process-local authentication state storage.
Package memory provides process-local authentication state storage.
Package mysql registers the MySQL dialect of the authentication state store.
Package mysql registers the MySQL dialect of the authentication state store.
Package postgres registers the PostgreSQL dialect of the authentication state store.
Package postgres registers the PostgreSQL dialect of the authentication state store.
Package redis provides a Redis and Valkey backed authstate.Store.
Package redis provides a Redis and Valkey backed authstate.Store.
Package sqlite registers the SQLite dialect of the authentication state store.
Package sqlite registers the SQLite dialect of the authentication state store.

Jump to

Keyboard shortcuts

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