Documentation
¶
Overview ¶
Package authstore re-exports the composite store interface from the standalone github.com/cplieger/auth/v2 library so subflux call sites can continue to refer to authstore.AuthStore without depending on the library's import path directly.
The auth library publishes store.Composite, composed of UserStore + SessionPersister + PasskeyStore + KeyStore + OIDCStateStore. Subflux uses the library's domain types (auth.User, auth.Session, auth.Key, auth.PasskeyCredential) directly, so AuthDB satisfies this interface with zero adapter glue.
This tiny package also keeps the historical role of breaking a test-time import cycle:
auth/_test → store/ → store/authdb/ → authstore/
authstore/ remains leaf and only imports the auth library.
Index ¶
- type AuthStore
- type Store
- func (s *Store) BatchUpdateSessionActivity(_ context.Context, tokenHashes []string, now time.Time) error
- func (s *Store) CleanupExpiredOIDCStates(_ context.Context, now time.Time, maxAge time.Duration) (int64, error)
- func (s *Store) CleanupExpiredSessions(_ context.Context, now time.Time, idleTimeout, absTimeout time.Duration) (int64, error)
- func (s *Store) Close() error
- func (s *Store) ConsumeOIDCState(_ context.Context, state string) (nonce, codeVerifier, redirectURI string, err error)
- func (s *Store) CreateAPIKey(_ context.Context, key *auth.Key) error
- func (s *Store) CreateOIDCState(_ context.Context, state, nonce, codeVerifier, redirectURI string) error
- func (s *Store) CreatePasskey(_ context.Context, cred *auth.PasskeyCredential) error
- func (s *Store) CreateSession(_ context.Context, sess *auth.Session) error
- func (s *Store) CreateUser(_ context.Context, user *auth.User) error
- func (s *Store) DeleteAPIKey(_ context.Context, id, userID int64) error
- func (s *Store) DeletePasskey(_ context.Context, id, userID int64) error
- func (s *Store) DeleteSession(_ context.Context, tokenHash string) error
- func (s *Store) DeleteUser(_ context.Context, id int64) error
- func (s *Store) DeleteUserSessions(_ context.Context, userID int64, exceptHash string) error
- func (s *Store) GetAPIKeyByHash(_ context.Context, hash string) (*auth.Key, error)
- func (s *Store) GetPasskeyByCredentialID(_ context.Context, credID []byte) (*auth.PasskeyCredential, error)
- func (s *Store) GetPasskeysByUserID(_ context.Context, userID int64) ([]auth.PasskeyCredential, error)
- func (s *Store) GetSessionByHash(_ context.Context, tokenHash string) (*auth.Session, error)
- func (s *Store) GetUserByEmail(_ context.Context, email string) (*auth.User, error)
- func (s *Store) GetUserByID(_ context.Context, id int64) (*auth.User, error)
- func (s *Store) GetUserByOIDCSub(_ context.Context, issuer, sub string) (*auth.User, error)
- func (s *Store) GetUserByUsername(_ context.Context, username string) (*auth.User, error)
- func (s *Store) ListAPIKeysByUserID(_ context.Context, userID int64) ([]auth.Key, error)
- func (s *Store) ListUsers(_ context.Context) ([]auth.User, error)
- func (s *Store) Open() error
- func (s *Store) PasskeyCountForUser(_ context.Context, userID int64) (int, error)
- func (s *Store) RenamePasskey(_ context.Context, id, userID int64, name string) error
- func (s *Store) SetSessionTimeouts(idle, absolute time.Duration)
- func (s *Store) UpdatePasskeyAfterLogin(_ context.Context, credID []byte, signCount uint32, flags auth.PasskeyFlags) error
- func (s *Store) UpdateSessionActivity(_ context.Context, tokenHash string, now time.Time) error
- func (s *Store) UpdateUser(_ context.Context, user *auth.User) error
- func (s *Store) UserCount(_ context.Context) (int, error)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AuthStore ¶
type AuthStore = authlibstore.Composite
AuthStore is the composite store interface implemented by the concrete authdb persistence layer and consumed by auth/ and server/.
This is a type alias of github.com/cplieger/auth/v2/store.Composite — the library is the single source of truth for the contract.
type Store ¶ added in v0.1.54
type Store struct {
// contains filtered or unexported fields
}
Store is the auth persistence layer. Durable state (users, passkeys, API keys) lives in bbolt buckets in the SAME file as the core store; ephemeral state (sessions, OIDC login states) lives only in the in-memory maps below.
The *bbolt.DB handle is SHARED with the core store (internal/boltstore), which owns it. The auth store never closes the handle — its Close only stops the background sweeper.
Bucket bootstrap is owned by the core store, which creates the auth buckets alongside the core buckets in one Update on Open.
func New ¶ added in v0.1.54
New constructs a Store over a *bbolt.DB handle shared with the core store. It allocates the in-memory ephemeral maps and the sweeper stop channel and seeds the sweeper timings with the package defaults, but does not start the sweeper; call Open for that.
func (*Store) BatchUpdateSessionActivity ¶ added in v0.1.54
func (s *Store) BatchUpdateSessionActivity(_ context.Context, tokenHashes []string, now time.Time) error
BatchUpdateSessionActivity touches the last-activity time of many sessions in one locked pass (memory only). Consumed by the server's session-activity batcher via sessionbatch.BatchUpdater (Requirement 16.7). An empty slice is a no-op, and absent hashes are skipped, matching the old store's per-hash UPDATE semantics.
func (*Store) CleanupExpiredOIDCStates ¶ added in v0.1.54
func (s *Store) CleanupExpiredOIDCStates(_ context.Context, now time.Time, maxAge time.Duration) (int64, error)
CleanupExpiredOIDCStates evicts every OIDC state whose creation instant is strictly before now-maxAge and returns the count evicted. The strict (exclusive) cutoff matches sessions.go's CleanupExpiredSessions, so a state exactly at the cutoff is kept rather than evicted (Requirement 10.3).
func (*Store) CleanupExpiredSessions ¶ added in v0.1.54
func (s *Store) CleanupExpiredSessions(_ context.Context, now time.Time, idleTimeout, absTimeout time.Duration) (int64, error)
CleanupExpiredSessions evicts every session past its idle OR absolute timeout and returns the count evicted (Requirement 10.3). A session is expired when last_activity is strictly before now-idleTimeout OR created_at is strictly before now-absTimeout, matching the exclusive cutoff comparison of the old SQLite delete (last_activity < idleCutoff OR created_at < absCutoff). Live sessions are kept.
func (*Store) Close ¶ added in v0.1.54
Close stops the background sweeper and waits for it to exit. It is safe to call multiple times and safe to call without a prior Open: the stop channel is closed exactly once via closeOnce, so a double Close never panics, and an unopened Store has no goroutine to wait on. Close NEVER closes the shared *bbolt.DB handle — the core store owns that handle and is the only place it is closed.
func (*Store) ConsumeOIDCState ¶ added in v0.1.54
func (s *Store) ConsumeOIDCState(_ context.Context, state string) (nonce, codeVerifier, redirectURI string, err error)
ConsumeOIDCState atomically returns and removes the OIDC state for the given token (single-use). The read and delete happen under one s.mu.Lock(), so two concurrent consumes of the same state can never both succeed: exactly one observes the entry and deletes it, the other sees it already gone. A second consume of an already-consumed (or never-created, or swept) state returns errOIDCStateNotFound (Requirement 16.3, anti-replay).
func (*Store) CreateAPIKey ¶ added in v0.1.54
CreateAPIKey inserts a new API key, rejecting a duplicate key hash with errConflict before any write (Requirement 9.3 hash uniqueness), and sets the surrogate ID on the supplied struct. CreatedAt is stamped to now when zero, mirroring the SQLite CURRENT_TIMESTAMP default. The primary row and its ix_apikey_user entry are written together in one Update, crash-durable on commit (Requirements 9.1, 9.2); on a conflict nothing is written.
func (*Store) CreateOIDCState ¶ added in v0.1.54
func (s *Store) CreateOIDCState(_ context.Context, state, nonce, codeVerifier, redirectURI string) error
CreateOIDCState records an in-flight OIDC login state in memory, keyed by the opaque state token. The stored instant is the creation time; OIDC states never touch disk (Requirement 10.1).
func (*Store) CreatePasskey ¶ added in v0.1.54
CreatePasskey inserts a new WebAuthn credential, rejecting a duplicate credential id with errConflict before any write (Requirement 9.3), and sets the surrogate ID on the supplied struct. CreatedAt is stamped to now when zero, mirroring the SQLite CURRENT_TIMESTAMP default. The primary row and its ix_passkey_user entry are written together in one Update, crash-durable on commit (Requirements 9.1, 9.2).
func (*Store) CreateSession ¶ added in v0.1.54
CreateSession stores a new session in memory, keyed by its token hash. A copy is stored so a later mutation of the caller's struct does not affect stored state. Sessions never touch disk (Requirement 10.1).
func (*Store) CreateUser ¶ added in v0.1.54
CreateUser inserts a new user, enforcing case-insensitive username uniqueness and (issuer, sub) uniqueness against the index buckets before the put, and sets the surrogate ID on the supplied struct (Requirements 9.2, 9.3). A duplicate username (case-insensitive) or duplicate (issuer, sub) returns errConflict and writes nothing. CreatedAt is stamped to now when zero and UpdatedAt is set equal to it, mirroring the SQLite CURRENT_TIMESTAMP defaults.
func (*Store) DeleteAPIKey ¶ added in v0.1.54
DeleteAPIKey removes the API key identified by surrogate id, but only when it belongs to userID (Requirement 16.4, mirroring the SQLite `DELETE ... WHERE id=? AND user_id=?`). It resolves the key hash via a user-scoped index walk, so it can only ever delete the supplied user's own key. It deletes the primary row and its ix_apikey_user entry in one Update; a non-matching (id, userID) is a no-op returning nil.
func (*Store) DeletePasskey ¶ added in v0.1.54
DeletePasskey removes the passkey identified by surrogate id, but only when it belongs to userID (Requirement 16.4). Like RenamePasskey it resolves the credential id via a user-scoped index walk, so it can only ever delete the supplied user's own passkey. It deletes the primary row and its ix_passkey_user entry in one Update; a non-matching (id, userID) is a no-op returning nil.
func (*Store) DeleteSession ¶ added in v0.1.54
DeleteSession removes the session with the given token hash. Deleting an absent session is a no-op returning nil (matching a DELETE affecting zero rows).
func (*Store) DeleteUser ¶ added in v0.1.54
DeleteUser removes the user and cascades to its passkeys, API keys, and sessions (Requirement 9.4). The durable parts — the user row, its ix_user_name / ix_user_oidc entries, and every child passkey / API key with their ix_passkey_user / ix_apikey_user entries — are deleted in one Update so the cascade is atomic and crash-durable on commit; the user's in-memory sessions are dropped after commit. Deleting a non-existent user is a no-op returning nil, matching the SQLite DELETE affecting zero rows.
func (*Store) DeleteUserSessions ¶ added in v0.1.54
DeleteUserSessions removes all of a user's sessions except the one identified by exceptHash (the keep-one exception used by "log out everywhere else" to keep the current session, Requirement 16.5). Pass exceptHash="" to drop all of the user's sessions (the DeleteUser cascade path); no real session carries an empty hash, so nothing is spuriously kept.
func (*Store) GetAPIKeyByHash ¶ added in v0.1.54
GetAPIKeyByHash looks up an API key by its hash (the API-auth hot path), returning (nil, nil) when not found (matching the old store's sql.ErrNoRows -> nil mapping). Decoding fails closed (auth bucket).
func (*Store) GetPasskeyByCredentialID ¶ added in v0.1.54
func (s *Store) GetPasskeyByCredentialID(_ context.Context, credID []byte) (*auth.PasskeyCredential, error)
GetPasskeyByCredentialID looks up a passkey by its credential id (the WebAuthn login hot path), returning (nil, nil) when not found (matching the old store's sql.ErrNoRows -> nil mapping). Decoding fails closed.
func (*Store) GetPasskeysByUserID ¶ added in v0.1.54
func (s *Store) GetPasskeysByUserID(_ context.Context, userID int64) ([]auth.PasskeyCredential, error)
GetPasskeysByUserID returns all of a user's passkeys, ordered by creation time then surrogate id (matching the old store's `ORDER BY created_at`). It walks ix_passkey_user for the user and dereferences each credential id; decoding fails closed (auth bucket).
func (*Store) GetSessionByHash ¶ added in v0.1.54
GetSessionByHash returns a copy of the session with the given token hash, or (nil, nil) when none exists (matching the old store's sql.ErrNoRows -> nil mapping). A copy is returned so callers cannot mutate the stored session through the returned pointer.
func (*Store) GetUserByEmail ¶ added in v0.1.54
GetUserByEmail looks up a user case-insensitively by email, returning (nil, nil) when not found (Requirement 16.1). email is not indexed (it is not unique in the schema), so this is a fail-closed scan of auth_users comparing the ASCII-folded email of each row.
func (*Store) GetUserByID ¶ added in v0.1.54
GetUserByID looks up a user by surrogate id, returning (nil, nil) when no such user exists (matching the old store's sql.ErrNoRows -> nil mapping).
func (*Store) GetUserByOIDCSub ¶ added in v0.1.54
GetUserByOIDCSub looks up a user by (issuer, sub) via ix_user_oidc, returning (nil, nil) when not found. An empty sub never matches, mirroring the SQLite partial index keyed only on rows with oidc_sub != ”. Matching on subject alone would be unsafe: a subject is unique only within its issuer.
func (*Store) GetUserByUsername ¶ added in v0.1.54
GetUserByUsername looks up a user case-insensitively by username via ix_user_name, returning (nil, nil) when not found (Requirement 16.1).
func (*Store) ListAPIKeysByUserID ¶ added in v0.1.54
ListAPIKeysByUserID returns all API keys for a user, ordered by creation date descending then surrogate id descending (newest first), matching the old store's `ORDER BY created_at DESC`. It walks ix_apikey_user for the user and dereferences each key hash; decoding fails closed (auth bucket).
func (*Store) ListUsers ¶ added in v0.1.54
ListUsers returns all users ordered by username (case-insensitive, then by id), matching the old store's `ORDER BY username` over a COLLATE NOCASE column. Decoding fails closed (auth bucket).
func (*Store) Open ¶ added in v0.1.54
Open starts the single background sweeper goroutine. It evicts expired sessions and OIDC states once per sweep interval until Close signals stop (Requirements 10.3, 10.4). Open is idempotent: a second call while the sweeper is already running is a no-op, so exactly one goroutine ever runs.
func (*Store) PasskeyCountForUser ¶ added in v0.1.54
PasskeyCountForUser returns the number of passkeys registered for a user (Requirement 16.7). It is a key-only prefix count over ix_passkey_user with no primary dereference.
func (*Store) RenamePasskey ¶ added in v0.1.54
RenamePasskey sets the friendly name of the passkey identified by surrogate id, but only when it belongs to userID (Requirement 16.4). It resolves the credential id by a user-scoped walk of ix_passkey_user, so a passkey owned by a different user is never visited and cannot be renamed. A non-matching (id, userID) is a no-op returning nil, matching the SQLite UPDATE affecting zero rows.
func (*Store) SetSessionTimeouts ¶ added in v0.1.134
SetSessionTimeouts overrides the sweeper's session idle/absolute timeouts with configured values. The composition root calls it after loading config, and the server's hot reload calls it again when the settings change, so the sweeper evicts on the SAME cutoffs the request-path session validator enforces (sweeper eviction of an in-memory session is a hard logout, so a shorter sweeper timeout would silently cap a longer configured one). Non-positive values leave the current timeout unchanged. Safe for concurrent use with a running sweeper.
func (*Store) UpdatePasskeyAfterLogin ¶ added in v0.1.54
func (s *Store) UpdatePasskeyAfterLogin(_ context.Context, credID []byte, signCount uint32, flags auth.PasskeyFlags) error
UpdatePasskeyAfterLogin persists the post-login authenticator flags and a durable, MONOTONIC sign_count: the stored value is set to max(stored, incoming) so a lower incoming counter (a replay or a cloned authenticator) can never regress it and defeat clone detection (Requirement 9.5, CVE-2023-45669). A missing credential is a no-op returning nil, matching the SQLite UPDATE that affects zero rows. The single-key Put is crash-durable on commit.
func (*Store) UpdateSessionActivity ¶ added in v0.1.54
UpdateSessionActivity touches a session's last-activity time (memory only, Requirement 10.2). An update for an absent session is a no-op returning nil, matching the SQLite UPDATE that affects zero rows.
func (*Store) UpdateUser ¶ added in v0.1.54
UpdateUser updates all mutable fields for the user, re-keying ix_user_name and ix_user_oidc when the username or (issuer, sub) change (delete-old, add-new), and re-checking uniqueness for the new keys (Requirement 9.3). It preserves the original CreatedAt and stamps UpdatedAt to now. An update for a non-existent id is a no-op returning nil, matching the SQLite UPDATE that affects zero rows.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package authstoretest provides a shared, engine-agnostic behavioral contract suite for the composite auth store (cplieger/auth/store.Composite).
|
Package authstoretest provides a shared, engine-agnostic behavioral contract suite for the composite auth store (cplieger/auth/store.Composite). |