Documentation
¶
Overview ¶
Package store defines the storage boundary of Auth-All. The application owns the database. An adapter implements these capability-oriented interfaces.
Index ¶
- Variables
- type APIKey
- type APIKeyStore
- type Account
- type AccountStore
- type CatalogInspector
- type Credential
- type ExtraFields
- type Migrator
- type OAuthState
- type OAuthStateStore
- type RateLimitCounter
- type RecoveryCodeStore
- type RowWriter
- type SchemaConfigurable
- type Session
- type SessionRevoker
- type SessionStore
- type SessionUserReader
- type Store
- type TOTP
- type TOTPStore
- type Token
- type TokenStore
- type User
- type UserAdminStore
- type UserListFilter
- type UserStore
Constants ¶
This section is empty.
Variables ¶
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. |