store

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Sep 10, 2026 License: MIT Imports: 5 Imported by: 0

Documentation

Overview

Package store defines the storage boundary of Auth-All. The application owns the database. An adapter implements these capability-oriented interfaces.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNotFound reports that the requested row does not exist.
	ErrNotFound = errors.New("authall/store: not found")
	// ErrConflict reports that a uniqueness constraint rejected the write.
	ErrConflict = errors.New("authall/store: conflict")
	// ErrInvalidCursor reports a page cursor that the store cannot read.
	ErrInvalidCursor = errors.New("authall/store: invalid cursor")
)

Sentinel storage errors.

Functions

This section is empty.

Types

type APIKey added in v0.3.0

type APIKey struct {
	ID     string
	UserID string
	// Name is the label of the owner.
	Name string
	// Start is the prefix and the first characters of the random part. A list
	// shows it, so a person recognizes the key.
	Start string
	// KeyHash is the SHA-256 hex digest of the plaintext key.
	KeyHash string
	// Role is the role of the key at creation. The effective role is the lower
	// of this role and the current role of the owner.
	Role       string
	CreatedAt  time.Time
	ExpiresAt  *time.Time
	LastUsedAt *time.Time
	RevokedAt  *time.Time
	RevokedBy  *string
}

APIKey is one machine credential of a user. The plaintext key exists only in the response of the create route. The store keeps the digest.

type APIKeyStore added in v0.3.0

type APIKeyStore interface {
	// CreateAPIKey inserts one key. It returns ErrConflict when the digest
	// exists already.
	CreateAPIKey(ctx context.Context, k *APIKey) error
	// APIKeyByHash returns the key of one digest and its owner in one round
	// trip. It returns ErrNotFound when no key matches.
	APIKeyByHash(ctx context.Context, keyHash string) (*APIKey, *User, error)
	// APIKeyByID returns one key by identifier.
	APIKeyByID(ctx context.Context, id string) (*APIKey, error)
	// ListAPIKeys returns every key of one owner, and the newest comes first.
	ListAPIKeys(ctx context.Context, userID string) ([]APIKey, error)
	// RevokeAPIKey ends one key. It returns ErrNotFound when the key is
	// already revoked or absent.
	RevokeAPIKey(ctx context.Context, id, byUserID string, at time.Time) error
	// TouchAPIKey writes the last use time of one key.
	TouchAPIKey(ctx context.Context, id string, at time.Time) error
}

APIKeyStore holds the machine credentials of the API keys plugin.

type Account

type Account struct {
	ID                string
	UserID            string
	Provider          string
	ProviderAccountID string
	CreatedAt         time.Time
	UpdatedAt         time.Time
}

Account is one external provider identity owned by a user.

type AccountStore

type AccountStore interface {
	// Create inserts an account. It returns ErrConflict when the provider
	// identity is already linked to a user.
	Create(ctx context.Context, a *Account) error
	GetByProviderAccount(ctx context.Context, provider, providerAccountID string) (*Account, error)
	ListByUser(ctx context.Context, userID string) ([]Account, error)
	// Delete removes one link. It returns ErrNotFound when no link exists.
	Delete(ctx context.Context, userID, provider string) error
}

AccountStore holds external provider identities.

type CatalogInspector added in v0.3.0

type CatalogInspector interface {
	// TableColumns returns the column names of one table. It returns false when
	// the table is absent.
	TableColumns(ctx context.Context, table string) ([]string, bool, error)
}

CatalogInspector reads the state of the database from the catalog of the engine. A host that applies the exported migrations with its own tool writes no Auth-All record, so the catalog is the only source of truth.

type Credential

type Credential struct {
	UserID       string
	PasswordHash string
	CreatedAt    time.Time
	UpdatedAt    time.Time
}

Credential is the password credential of one user.

type ExtraFields added in v0.3.0

type ExtraFields struct {
	// contains filtered or unexported fields
}

ExtraFields holds the host-owned fields of one user. The key is the field name.

func NewExtraFields added in v0.3.0

func NewExtraFields(values map[string]any) *ExtraFields

NewExtraFields returns a field set with the given values.

func (*ExtraFields) Get added in v0.3.0

func (e *ExtraFields) Get(name string) (any, bool)

Get returns one value. The second result is false when the field is absent.

func (*ExtraFields) Names added in v0.3.0

func (e *ExtraFields) Names() []string

Names returns every field name in a deterministic order.

func (*ExtraFields) Set added in v0.3.0

func (e *ExtraFields) Set(name string, value any)

Set writes one value.

type Migrator

type Migrator interface {
	// Dialect reports the SQL flavor of the adapter.
	Dialect() schema.Dialect
	// Plan returns the statements that are not applied yet.
	Plan(ctx context.Context, s *schema.Schema) ([]schema.Statement, error)
	// Apply runs the pending statements and records them.
	Apply(ctx context.Context, s *schema.Schema) ([]schema.Statement, error)
	// Check returns an actionable error when the database schema is missing or
	// outdated.
	Check(ctx context.Context, s *schema.Schema) error
}

Migrator applies the effective schema. It never runs automatically.

type OAuthState

type OAuthState struct {
	ID         string
	StateHash  string
	Provider   string
	Verifier   string
	Nonce      string
	RedirectTo string
	LinkUserID *string
	CreatedAt  time.Time
	ExpiresAt  time.Time
	ConsumedAt *time.Time
}

OAuthState is one pending OAuth authorization request.

type OAuthStateStore

type OAuthStateStore interface {
	Create(ctx context.Context, s *OAuthState) error
	// Consume atomically marks one unconsumed and unexpired state as consumed
	// and returns it.
	Consume(ctx context.Context, stateHash string, now time.Time) (*OAuthState, error)
	DeleteExpired(ctx context.Context, before time.Time) (int, error)
}

OAuthStateStore holds pending OAuth authorization requests.

type RateLimitCounter added in v0.3.0

type RateLimitCounter interface {
	// CountAttempt adds one attempt to the counter of key and returns the new
	// count and the start of the window. It starts a new window when the
	// running window ended before now minus window.
	CountAttempt(ctx context.Context, key string, window time.Duration, now time.Time) (count int, windowStart time.Time, err error)
	// CleanupRateLimits removes every counter whose window ended before the
	// given time. It returns the number of removed rows.
	CleanupRateLimits(ctx context.Context, before time.Time) (int, error)
}

RateLimitCounter keeps the rate-limit counters of the store-backed limiter. One statement counts one attempt, so the count is atomic across instances.

type RecoveryCodeStore added in v0.2.0

type RecoveryCodeStore interface {
	// ReplaceAll removes every code of the user and writes the supplied
	// hashes. It runs in one transaction, so a failure never leaves the user
	// with no codes.
	ReplaceAll(ctx context.Context, userID string, hashes []string) error
	// Consume removes one code of one user and reports whether a row matched.
	//
	// The match and the removal are one atomic operation. Two concurrent calls
	// that carry the same code must produce at most one true, because a
	// recovery code carries a complete sign-in.
	//
	// An unknown code reports false and no error.
	Consume(ctx context.Context, userID, codeHash string) (bool, error)
	// CountByUser returns the number of unused codes of one user.
	CountByUser(ctx context.Context, userID string) (int, error)
	// DeleteByUser removes every code of one user and returns the count.
	DeleteByUser(ctx context.Context, userID string) (int, error)
}

RecoveryCodeStore holds the recovery codes of the second factor.

A recovery code is a first factor and a second factor at once, so the store keeps only the SHA-256 hash. A recovery code carries about 49 bits from a random source, so it needs no slow password hash.

type RowWriter added in v0.3.0

type RowWriter interface {
	// InsertRow inserts one row. It returns ErrConflict when a uniqueness
	// constraint refuses the row.
	InsertRow(ctx context.Context, table string, columns []string, values []any) error
}

RowWriter writes one row of a table that a plugin owns. A plugin uses it for a small table that needs no typed store, for example the bootstrap guard.

The caller must supply a fixed column list and no value from a request, so the statement carries no injected SQL.

type SchemaConfigurable added in v0.3.0

type SchemaConfigurable interface {
	// UseSchema sets the physical names and types. It reports an error when the
	// store cannot serve the options.
	UseSchema(o schema.Options) error
}

SchemaConfigurable accepts the physical schema options of the host. Auth-All calls it during construction when the host uses a table prefix or another identifier type.

type Session

type Session struct {
	ID         string
	UserID     string
	TokenHash  string
	CreatedAt  time.Time
	ExpiresAt  time.Time
	LastSeenAt time.Time
}

Session is one database-backed opaque session. TokenHash never holds a plaintext token.

type SessionRevoker added in v0.3.0

type SessionRevoker interface {
	// DeleteSessionsExcept removes every session of the owner of sessionID,
	// except that session. It returns the number of removed sessions, and it
	// returns zero when the session does not exist.
	DeleteSessionsExcept(ctx context.Context, sessionID string) (int, error)
}

SessionRevoker revokes the other sessions of one owner in one statement.

type SessionStore

type SessionStore interface {
	Create(ctx context.Context, s *Session) error
	GetByTokenHash(ctx context.Context, tokenHash string) (*Session, error)
	// ListByUser returns every session of one user, and the newest comes
	// first. It returns an empty result for a user without a session.
	ListByUser(ctx context.Context, userID string) ([]Session, error)
	// Touch updates last_seen_at. It returns ErrNotFound when the session no
	// longer exists, so a revoked session cannot be resurrected.
	Touch(ctx context.Context, id string, at time.Time) error
	Delete(ctx context.Context, id string) error
	DeleteByUser(ctx context.Context, userID string) (int, error)
	DeleteExpired(ctx context.Context, before time.Time) (int, error)
}

SessionStore holds database-backed sessions.

type SessionUserReader added in v0.3.0

type SessionUserReader interface {
	// SessionWithUser returns the session of a token hash and its user. It
	// returns ErrNotFound when no session matches.
	SessionWithUser(ctx context.Context, tokenHash string) (*Session, *User, error)
}

SessionUserReader reads a session and its user in one round trip. A store that does not implement it costs one more round trip for each request.

type Store

type Store interface {
	Users() UserStore
	Accounts() AccountStore
	Sessions() SessionStore
	Tokens() TokenStore
	OAuthStates() OAuthStateStore
	TOTP() TOTPStore
	RecoveryCodes() RecoveryCodeStore

	// Transaction runs fn inside one database transaction. The Store passed to
	// fn performs every operation inside that transaction.
	Transaction(ctx context.Context, fn func(Store) error) error

	// Migrator returns the schema migrator of the adapter.
	Migrator() Migrator

	// Close releases adapter resources. It does not close a database handle
	// owned by the application.
	Close() error
}

Store is the storage boundary of Auth-All.

type TOTP added in v0.2.0

type TOTP struct {
	UserID string
	Secret string
	// ConfirmedAt is nil until the user proves one code. An unconfirmed
	// enrolment never authenticates a sign-in.
	ConfirmedAt *time.Time
	// LastStep is the last accepted time step. The sign-in gate refuses a step
	// that is not greater than this value, which stops a replay of one code
	// inside its own window.
	LastStep  int64
	CreatedAt time.Time
	UpdatedAt time.Time
}

TOTP is the time-based one-time password enrolment of one user.

Secret holds the base32 shared secret. Auth-All does not encrypt it, because Auth-All holds no application key, and a key that the library invents lives in the same database as the secret. An application that needs encryption at rest applies it at the column or at the volume.

type TOTPStore added in v0.2.0

type TOTPStore interface {
	// Get returns the enrolment of one user. It returns ErrNotFound when the
	// user holds no secret.
	Get(ctx context.Context, userID string) (*TOTP, error)
	// Upsert writes the enrolment of one user and replaces any existing row.
	// A replacement clears the confirmation and the last step, because a new
	// secret starts a new enrolment.
	Upsert(ctx context.Context, t *TOTP) error
	// Confirm marks the enrolment of one user as proven. It returns
	// ErrNotFound when the user holds no secret.
	Confirm(ctx context.Context, userID string, at time.Time) error
	// AdvanceStep records step when it is greater than the stored step, and
	// reports whether the write happened.
	//
	// The comparison and the write are one atomic operation. Two concurrent
	// calls that carry the same step must produce at most one true, because a
	// read followed by a later write would let an attacker replay one stolen
	// code across parallel requests.
	//
	// It returns ErrNotFound when the user holds no secret.
	AdvanceStep(ctx context.Context, userID string, step int64) (bool, error)
	// Delete removes the enrolment of one user. It returns ErrNotFound when
	// the user holds no secret.
	Delete(ctx context.Context, userID string) error
}

TOTPStore holds the TOTP enrolment of each user.

type Token

type Token struct {
	ID         string
	UserID     *string
	Kind       string
	Identifier string
	TokenHash  string
	CreatedAt  time.Time
	ExpiresAt  time.Time
	ConsumedAt *time.Time
}

Token is one one-time token. TokenHash never holds a plaintext token.

type TokenStore

type TokenStore interface {
	Create(ctx context.Context, t *Token) error
	// Consume atomically marks one unconsumed and unexpired token as consumed
	// and returns it. Two concurrent calls for the same token must produce at
	// most one success. It returns ErrNotFound otherwise.
	Consume(ctx context.Context, kind, tokenHash string, now time.Time) (*Token, error)
	// Get returns a token without consuming it.
	Get(ctx context.Context, kind, tokenHash string) (*Token, error)
	// DeleteByIdentifier removes every outstanding token of one kind for one
	// identifier.
	DeleteByIdentifier(ctx context.Context, kind, identifier string) error
	DeleteExpired(ctx context.Context, before time.Time) (int, error)
}

TokenStore holds one-time tokens.

type User

type User struct {
	ID              string
	Email           string
	EmailNormalized string
	EmailVerifiedAt *time.Time
	DisplayName     string
	ImageURL        string
	CreatedAt       time.Time
	UpdatedAt       time.Time
	// Role is the role name of the user. An empty value means the default role
	// of the roles plugin.
	Role string
	// DisabledAt blocks sign-in and every credential of the user when it is
	// not nil.
	DisabledAt *time.Time
	// MustChangePassword blocks every protected route until the user sets a
	// new password.
	MustChangePassword bool
	// Extra holds the host-owned user fields. It is nil when the host declared
	// no field.
	//
	// The field is a pointer, so a User value stays comparable. A v1
	// application that compares two users keeps its behavior.
	Extra *ExtraFields
}

User is one Auth-All user.

type UserAdminStore added in v0.3.0

type UserAdminStore interface {
	// ListUsers returns one page of users and the cursor of the next page. An
	// empty cursor means that no page follows. The order is stable, so no user
	// repeats and no user is lost.
	ListUsers(ctx context.Context, f UserListFilter) (users []User, next string, err error)
	// LockEnabledUsersWithRole returns the identifiers of the enabled users of
	// one role and locks the rows until the transaction ends.
	//
	// The caller must run it inside a write transaction. The lock makes the
	// last-admin guard safe under concurrent requests.
	LockEnabledUsersWithRole(ctx context.Context, role string) ([]string, error)
}

UserAdminStore reads and locks the users of an administrative operation.

type UserListFilter added in v0.3.0

type UserListFilter struct {
	// EmailPrefix keeps the users whose normalized email starts with the
	// value.
	EmailPrefix string
	// Role keeps the users of one role. A nil value keeps every role. An
	// empty string keeps the users whose role column is empty.
	Role *string
	// Disabled keeps the disabled users when it is true, and the enabled
	// users when it is false. A nil value keeps both.
	Disabled *bool
	// Limit is the number of returned users.
	Limit int
	// Cursor continues an earlier page. An empty value starts at the first
	// user.
	Cursor string
}

UserListFilter selects and pages the users of an administrative list.

type UserStore

type UserStore interface {
	// Create inserts a user. It returns ErrConflict when the normalized email
	// is already taken.
	Create(ctx context.Context, u *User) error
	GetByID(ctx context.Context, id string) (*User, error)
	GetByNormalizedEmail(ctx context.Context, normalized string) (*User, error)
	// Update writes the mutable user fields.
	Update(ctx context.Context, u *User) error
	// Delete removes a user and every owned row.
	Delete(ctx context.Context, id string) error

	GetCredential(ctx context.Context, userID string) (*Credential, error)
	// SetCredential inserts or replaces the password credential of a user.
	SetCredential(ctx context.Context, c *Credential) error
	DeleteCredential(ctx context.Context, userID string) error
}

UserStore holds users and password credentials.

Directories

Path Synopsis
Package postgres provides the PostgreSQL storage adapter for Auth-All.
Package postgres provides the PostgreSQL storage adapter for Auth-All.
Package sqlite provides the SQLite storage adapter for Auth-All.
Package sqlite provides the SQLite storage adapter for Auth-All.
Package storetest holds the behavioral contract suite that every Auth-All storage adapter must pass.
Package storetest holds the behavioral contract suite that every Auth-All storage adapter must pass.

Jump to

Keyboard shortcuts

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