Help us shape the future of Go! Take the 2026 Go Developer Survey

store

package
v1.9.0 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Overview

Package store defines the aggregate persistence interface. Each subsystem (user, session, account, app, organization, device, webhook, notification, environment) defines its own store interface. The composite Store composes them all. Backends: PostgreSQL, SQLite, MongoDB, and Memory.

Index

Constants

View Source
const (
	RetentionSessions             = "sessions"
	RetentionVerifications        = "verifications"
	RetentionPasswordResets       = "password_resets"
	RetentionRevokedRefreshTokens = "revoked_refresh_tokens"
	RetentionDeviceCodes          = "device_codes"
	RetentionAuthCodes            = "auth_codes"
)

Retention kinds: the names the sweeper and plugins use for each class of row when asking for its cutoff.

Variables

View Source
var ErrConflict = errors.New("authsome: conflict")

ErrConflict is returned when a write would violate a backend uniqueness constraint that doesn't map to a more specific account-level sentinel (ErrEmailTaken, ErrUsernameTaken, etc.). The mongo / postgres / sqlite stores translate raw duplicate-key errors into this so the API layer can surface a 409 without leaking the raw constraint name and key value (which would otherwise reveal index structure and the existence of the colliding row).

View Source
var ErrNotFound = errors.New("authsome: not found")

ErrNotFound is returned when a requested entity does not exist.

Functions

func DrainExpired

func DrainExpired(ctx context.Context, batch int, del func(ctx context.Context, batch int) (int64, error)) (int64, error)

DrainExpired calls del with the batch size until a call removes fewer rows than the batch, or errors, and returns the total removed. A batch of zero or less means one unbounded call. This is the loop every sweeper runs, so the shape of "keep going until short" lives in one place.

func HashToken

func HashToken(tok string) string

HashToken returns the hex-encoded SHA-256 of a presented credential.

Every token column in every backend stores this digest instead of the plaintext: session and refresh tokens, verification codes, password reset tokens, invitation tokens and OAuth2 codes. The tokens themselves carry at least 128 bits of entropy, so an unsalted digest is enough to make a copy of the database useless while keeping lookups a single indexed equality.

The refresh-token revocation set already keys on this exact digest, which is why the two must never diverge: a session's RefreshTokenHash is the key the replay detector consults.

func KVExpiry

func KVExpiry(now time.Time, ttl time.Duration) int64

KVExpiry converts a TTL from now into the unix-millisecond expiry every backend stores. A zero or negative ttl yields a row that is already expired; every caller passes a positive ttl, and the conformance suite uses negative ones to plant expired rows.

Types

type KV

type KV interface {
	// KVGet returns the value stored under key, or ErrNotFound when the key
	// is absent or expired.
	KVGet(ctx context.Context, key string) ([]byte, error)

	// KVSet stores value under key for ttl, replacing any existing value.
	KVSet(ctx context.Context, key string, value []byte, ttl time.Duration) error

	// KVSetNX stores value under key for ttl only when the key is absent or
	// expired, and reports whether it did.
	KVSetNX(ctx context.Context, key string, value []byte, ttl time.Duration) (bool, error)

	// KVIncrement adds one to the counter under key and returns the new
	// count. An absent or expired key starts at one with the given ttl; an
	// existing key keeps its expiry.
	KVIncrement(ctx context.Context, key string, ttl time.Duration) (int64, error)

	// KVCounter returns the counter under key without changing it, or zero
	// when the key is absent or expired.
	KVCounter(ctx context.Context, key string) (int64, error)

	// KVDelete removes key. Deleting an absent key is not an error.
	KVDelete(ctx context.Context, key string) error

	// KVDeleteExpired removes every row whose expiry is before now and
	// returns how many it removed.
	KVDeleteExpired(ctx context.Context, now time.Time) (int64, error)
}

KV is a small shared key-value space with expiry that every backend provides on the authsome_kv table. Rate limiting, lockout and ceremony state are built on it so those controls hold across replicas instead of living in one process's memory.

Expiry is stored as unix milliseconds on every backend so comparisons do not depend on timestamp parsing. Expired rows are invisible to every read and are reclaimed by KVDeleteExpired.

type LegacyTokenHasher

type LegacyTokenHasher interface {
	// HashLegacyTokens rewrites up to batch rows, across sessions,
	// verifications, password resets and invitations, that still hold a
	// plaintext credential with no hash. It reports how many rows it
	// converted; zero means the sweep is complete.
	HashLegacyTokens(ctx context.Context, batch int) (int64, error)
}

LegacyTokenHasher converts credential rows written before token hashing.

Lookups already upgrade a legacy row the first time it is presented, but a row nobody presents again would keep its plaintext forever. The engine sweeps these at start so the window in which a database copy still holds live credentials closes on its own.

type Paged

type Paged interface {
	ListUserSessionsPage(ctx context.Context, userID id.UserID, opts page.Opts) (page.Page[*session.Session], error)
	ListOrganizationsPage(ctx context.Context, appID id.AppID, opts page.Opts) (page.Page[*organization.Organization], error)
	ListUserOrganizationsPage(ctx context.Context, userID id.UserID, opts page.Opts) (page.Page[*organization.Organization], error)
	ListMembersPage(ctx context.Context, orgID id.OrgID, opts page.Opts) (page.Page[*organization.Member], error)
	ListInvitationsPage(ctx context.Context, orgID id.OrgID, opts page.Opts) (page.Page[*organization.Invitation], error)
	ListTeamsPage(ctx context.Context, orgID id.OrgID, opts page.Opts) (page.Page[*organization.Team], error)
}

Paged is the bounded twin of the unbounded list methods, for the request path: every method returns at most opts.Limit rows, newest first, and a cursor for the rest. The unbounded methods stay for internal callers that need every row (cascades, cap enforcement, adoption). The domain store interfaces (session.Store, organization.Store) carry the same methods; this lists them in one place.

type Retention

type Retention interface {
	// DeleteExpiredSessions removes sessions whose access token and refresh
	// token both expired before the given instant. A session whose refresh
	// token is still valid can be refreshed and is kept.
	DeleteExpiredSessions(ctx context.Context, before time.Time, batch int) (int64, error)

	// DeleteExpiredVerifications removes verification codes and links,
	// consumed or not, that expired before the given instant.
	DeleteExpiredVerifications(ctx context.Context, before time.Time, batch int) (int64, error)

	// DeleteExpiredPasswordResets removes password reset tokens, consumed or
	// not, that expired before the given instant.
	DeleteExpiredPasswordResets(ctx context.Context, before time.Time, batch int) (int64, error)

	// DeleteExpiredRevokedRefreshTokens removes replay-detection records
	// revoked before the given instant. Once the refresh token they name has
	// passed its own lifetime the record has nothing left to detect.
	DeleteExpiredRevokedRefreshTokens(ctx context.Context, before time.Time, batch int) (int64, error)
}

Retention is the batched removal of rows whose useful life has ended. Expired sessions, spent verification codes and reset tokens, and old revocation records are evidence of nothing once their window has closed, and keeping them is a liability: a copy of the database should hold as little as the service still needs.

Every method deletes rows whose expiry (or revocation, for revoked refresh tokens) falls before the given instant, removes at most batch rows in one call, and reports how many it removed. A batch of zero or less removes every matching row. Callers loop until a call comes back short of the batch, which keeps each statement small enough not to hold locks or swamp the write-ahead log on a large table.

type Store

type Store interface {
	user.Store
	session.Store
	account.Store
	app.Store
	organization.Store
	device.Store
	webhook.Store
	notification.Store
	apikey.Store
	environment.Store
	formconfig.Store
	formconfig.BrandingStore
	appsessionconfig.Store
	appclientconfig.Store
	settings.Store
	serviceaccount.Store
	principal.Store
	KV
	LegacyTokenHasher
	Retention
	Paged

	// Migrate runs all schema migrations. Extra migration groups (e.g. from
	// plugins) are appended to the core group and orchestrated together.
	Migrate(ctx context.Context, extraGroups ...*migrate.Group) error

	// Ping checks database connectivity.
	Ping(ctx context.Context) error

	// Close closes the store connection.
	Close() error
}

Store is the aggregate persistence interface. Each subsystem store is a composable interface. A single backend (postgres, sqlite, mongo, memory) implements all of them.

type TTLIndexer

type TTLIndexer interface {
	// EnsureTTLIndexes creates or updates the expiry index for each kind in
	// ttl. A kind missing from the map, or mapped to zero or less, gets no
	// index (and an existing one is left alone).
	EnsureTTLIndexes(ctx context.Context, ttl map[string]time.Duration) error
}

TTLIndexer is offered by a store whose database can expire rows on its own. The sweeper calls it once at start with the retention window for each kind, so the database drops what the sweeper would otherwise have to.

Directories

Path Synopsis
Package memory provides an in-memory implementation of store.Store for testing.
Package memory provides an in-memory implementation of store.Store for testing.
Package mongo implements the AuthSome store interface using MongoDB via Grove ORM.
Package mongo implements the AuthSome store interface using MongoDB via Grove ORM.
Package postgres implements the AuthSome store interface using grove ORM (PostgreSQL).
Package postgres implements the AuthSome store interface using grove ORM (PostgreSQL).
Package sqlite implements the AuthSome store interface using grove ORM (SQLite).
Package sqlite implements the AuthSome store interface using grove ORM (SQLite).
Package storetest provides a backend-agnostic conformance suite for the store.Store interface.
Package storetest provides a backend-agnostic conformance suite for the store.Store interface.

Jump to

Keyboard shortcuts

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