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

lockout

package
v0.5.3 Latest Latest
Warning

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

Go to latest
Published: Nov 3, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package lockout provides typed failed-credential windows and temporary lockout. It is distinct from request-rate limiting: concurrent admitted attempts can still complete. Pair it with ratelimit before expensive credential work.

Index

Constants

View Source
const KeyFeature keyspace.Feature = "lockout"

KeyFeature is the physical address family owned by this adapter.

View Source
const MaxDuration = ratelimit.MaxWindow
View Source
const MaxFailures uint32 = 10000

Variables

This section is empty.

Functions

func ValidateOperation

func ValidateOperation(ctx context.Context, key Key, policy Policy) error

ValidateOperation checks context cancellation, key validity and lockout policy.

Types

type Admission

type Admission struct {
	Decision Decision
	Snapshot Snapshot
}

Admission returns a snapshot only for an allowed attempt.

func (Admission) Validate

func (a Admission) Validate(p Policy) error

Validate checks that admission is either allowed with a valid snapshot or locked without one.

type Backend

type Backend interface {
	// LockoutBegin checks the current lock and returns a snapshot for an admitted attempt,
	// using candidate only when creating a window.
	LockoutBegin(context.Context, Key, Policy, Generation) (Admission, error)
	// LockoutFinish applies one admitted outcome after checking the current generation,
	// revision and lock. A stale success cannot clear newer failures.
	LockoutFinish(context.Context, Key, Policy, Snapshot, Outcome) (Decision, error)
	// LockoutReset removes the key's state and invalidates outstanding snapshots,
	// reporting whether state existed.
	LockoutReset(context.Context, Key, Policy) (bool, error)
}

Backend owns atomic transitions and the authority clock. Begin records the candidate generation only when creating a new window. Finish must use the exact admitted Snapshot once; it checks current lock/expiry even after successful credential verification. Success clears only when the revision still matches. Reset invalidates all outstanding snapshots for that key. Live policy changes, corrupt state and backward clocks fail closed. No operation retries uncertain mutations or falls back to another authority. The caller owns lifecycle.

Backend methods are trusted adapter boundaries, not public credential proof. An error always returns a zero result. External adapters must provide bounded state, atomic count+expiry updates and distinct generation values after reset.

type Code

type Code string

Code identifies a lockout failure category.

const (
	Locked      Code = "login_locked_out"
	Expired     Code = "login_attempt_expired"
	Unavailable Code = "login_protection_unavailable"
)

func (Code) Error

func (c Code) Error() string

Error returns the lockout error code text.

type Config

type Config struct {
	Namespace                                   keyspace.Namespace
	MaxKeyBytes, MaxDeclarations, MaxConcurrent int
	Timeout                                     time.Duration
}

Config bounds all key encoding, backend calls and credential callbacks held by a borrowing store. Timeout must fit the intended verification work. A callback retaining control after cancellation also retains its concurrency slot.

func DefaultConfig

func DefaultConfig(namespace keyspace.Namespace) Config

DefaultConfig returns default key, declaration, concurrency and timeout bounds for namespace.

func (Config) Validate

func (c Config) Validate() error

Validate checks the namespace and key, declaration, concurrency and timeout bounds.

type Decision

type Decision struct {
	Status     Status
	RetryAfter time.Duration
	Triggered  bool
}

Decision describes the authority's state after an operation. RetryAfter is relative to that operation. Triggered is true only for the failure that starts a lock; it can drive an in-process notification, never a durable delivery claim.

func (Decision) Validate

func (d Decision) Validate(p Policy) error

Validate checks status and retry metadata against policy, returning fault.Internal for an invalid adapter decision.

type Declaration

type Declaration[K any] struct {
	// contains filtered or unexported fields
}

Declaration retains the submitted login key type. Include tenant/provider identity in its codec or family name, and use the same normalization as lookup. Missing and existing accounts must use the same policy and key construction.

func Define

func Define[K any](name Name, codec keyspace.Codec[K], policy Policy) Declaration[K]

Define declares a single-key throttle, for example MFA attempts keyed by an already authenticated subject. Submitted login identifiers use DefineLogin.

func DefineLogin

func DefineLogin[K any](name Name, codec keyspace.Codec[K], limits Limits) Declaration[K]

DefineLogin declares a client-aware throttle for submitted login identifiers. Failures count per (account, trusted client IP) with limits.PerClient, plus the Account and Address ceilings. The client IP comes from the request attribution established by the Foundry HTTP server (trusted proxy rules apply); without one, attempts share an "unknown" client and skip the Address ceiling. Policy reports limits.PerClient.

func (Declaration[K]) Bind

func (d Declaration[K]) Bind(store *Store) (Throttle[K], error)

Bind registers the exact declaration in store and returns its throttle, rejecting conflicting names or exhausted declaration capacity.

func (Declaration[K]) Limits

func (d Declaration[K]) Limits() (Limits, bool)

Limits returns the client-aware limits of a DefineLogin declaration.

func (Declaration[K]) Name

func (d Declaration[K]) Name() Name

Name returns the family name, or an empty name for an undefined declaration.

func (Declaration[K]) Policy

func (d Declaration[K]) Policy() Policy

Policy returns the declared policy, or a zero policy for an undefined declaration.

func (Declaration[K]) Validate

func (d Declaration[K]) Validate() error

Validate checks the family name, key codec and single or layered policies.

type Generation

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

Generation is a random window identity. It prevents an expired/reset window from being confused with a newly created window that has the same counters. This adapter token is not an authentication credential.

func NewGeneration

func NewGeneration() (Generation, error)

NewGeneration creates a random generation digest for a new lockout window.

func ParseGeneration

func ParseGeneration(text string) (Generation, error)

ParseGeneration restores a generation from its hexadecimal digest.

func (Generation) GoString

func (g Generation) GoString() string

GoString returns the redacted generation label for Go-syntax formatting.

func (Generation) String

func (Generation) String() string

String returns a generation label without exposing the token.

func (Generation) Token

func (g Generation) Token() string

Token exposes the generation's hexadecimal adapter representation.

func (Generation) Validate

func (g Generation) Validate() error

Validate checks that the generation contains a valid digest.

type Key

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

Key is an adapter address, distinct from cache, lease and request-limit keys. Logical identifiers are hashed by the shared address codec, not logged raw.

func NewKey

func NewKey(namespace keyspace.Namespace, name Name, logical string) (Key, error)

NewKey validates and constructs an adapter address for namespace, name and logical key.

func (Key) Namespace

func (k Key) Namespace() keyspace.Namespace

Namespace returns the key's application namespace.

func (Key) String

func (k Key) String() string

String returns the physical lockout address.

func (Key) Validate

func (k Key) Validate() error

Validate checks the adapter address.

type Limits

type Limits struct {
	PerClient Policy
	Account   Policy
	Address   value.Optional[Policy]
}

Limits is the client-aware login throttle. PerClient counts failures for one account from one client address, so a single source cannot keep a victim locked out for everyone. Account is a higher per-account ceiling that still bounds distributed guessing; its tradeoff is that an attacker spreading failures across many addresses can lock one targeted account for LockFor.

Address is an optional per-client-address ceiling across all accounts that bounds credential spraying from one source. It is off by default: every user behind a shared address (carrier-grade NAT, a corporate proxy or VPN) shares it, so it can lock all of them out, including correct passwords, and with misconfigured trusted proxies every request appears to come from the proxy and one ceiling covers the whole site. Enable it (for example with DefaultAddressPolicy) only when client addresses are trusted and rarely shared, and prefer HTTP request rate limits for spraying. Each enabled ceiling must be at least the PerClient threshold.

func DefaultLimits

func DefaultLimits() Limits

DefaultLimits enables the pair and account windows; Address is off.

func (Limits) Validate

func (l Limits) Validate() error

Validate checks each policy and requires account and optional address ceilings at least as high as the per-client threshold.

type Name

type Name string

Name identifies a lockout declaration family.

type Notice

type Notice[K any] struct {
	// contains filtered or unexported fields
}

Notice retains the declaration's concrete key for an explicit domain event or operational notification. Routine formatting/JSON hides account identifiers. This is an in-process observation after state commits, not durable delivery.

func (Notice[K]) Format

func (Notice[K]) Format(s fmt.State, _ rune)

Format writes the label "login lockout notice" without exposing the value.

func (Notice[K]) Key

func (n Notice[K]) Key() K

Key returns the typed key whose lockout was observed.

func (Notice[K]) RetryAfter

func (n Notice[K]) RetryAfter() time.Duration

RetryAfter returns the lock duration recorded in the notice.

type Outcome

type Outcome uint8

Outcome identifies whether an admitted credential attempt failed or succeeded.

const (
	Failed Outcome = iota + 1
	Succeeded
)

func (Outcome) Validate

func (o Outcome) Validate() error

Validate accepts only Failed and Succeeded.

type Policy

type Policy struct {
	MaxFailures     uint32
	Window, LockFor time.Duration
}

Policy counts completed failures in a window starting at the first admitted attempt. Reaching MaxFailures locks until LockFor elapses. Denials never extend that expiry. A successful attempt clears only failures observed at its start; later failures are retained. Window expiry or an explicit Reset invalidates outstanding attempts, which must be retried rather than granting authority.

func DefaultAddressPolicy

func DefaultAddressPolicy() Policy

DefaultAddressPolicy is the documented opt-in per-address ceiling: 100 failures across all accounts in 15 minutes lock that address for 15 minutes.

func DefaultPolicy

func DefaultPolicy() Policy

DefaultPolicy returns a five-failure threshold with a fifteen-minute window and lock duration.

func (Policy) Validate

func (p Policy) Validate() error

Validate checks the failure threshold and whole-millisecond window and lock durations.

type Rejection

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

Rejection carries a safe relative retry duration, never the submitted key. Runtime construction follows a validated backend decision.

func (*Rejection) Error

func (e *Rejection) Error() string

Error returns the Locked code without formatting the cause.

func (*Rejection) Is

func (e *Rejection) Is(target error) bool

Is reports whether target is Locked.

func (*Rejection) RetryAfter

func (e *Rejection) RetryAfter() time.Duration

RetryAfter returns the remaining lock duration, or zero for a nil rejection.

func (*Rejection) Triggered

func (e *Rejection) Triggered() bool

Triggered reports whether this attempt's failure started the lock, rather than being denied by an existing lock.

func (*Rejection) Unwrap

func (e *Rejection) Unwrap() error

Unwrap returns the underlying cause, or nil for a nil rejection.

type Snapshot

type Snapshot struct {
	Generation Generation
	Revision   uint32
}

Snapshot is an admission's immutable adapter observation. It is not a lease or permission to skip the final state check. Revision changes on completed failure and successful clearing, so a late success cannot erase newer failures.

func (Snapshot) Validate

func (s Snapshot) Validate() error

Validate checks the snapshot's generation token.

type Status

type Status uint8

Status identifies an allowed, locked or expired lockout decision.

const (
	StatusAllowed Status = iota + 1
	StatusLocked
	StatusExpired
)

type Store

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

Store borrows a backend without I/O or background work. One store may bind distinct password, MFA and recovery declarations; names isolate their policy.

func NewStore

func NewStore(backend Backend, config Config) (*Store, error)

NewStore validates and borrows backend and creates bounded operation admission and a declaration registry.

func (*Store) Namespace

func (s *Store) Namespace() keyspace.Namespace

Namespace returns the configured namespace, or its zero value for a nil store.

type Throttle

type Throttle[K any] struct {
	// contains filtered or unexported fields
}

Throttle owns one factor's failed-attempt policy, retaining the submitted key type. Its callbacks must not issue credentials: Run checks current lock state after verification before returning true. Already admitted attempts can overlap; use request-rate limits to bound that work independently.

func (Throttle[K]) Limits

func (t Throttle[K]) Limits() (Limits, bool)

Limits returns the client-aware limits when this throttle came from DefineLogin.

func (Throttle[K]) Name

func (t Throttle[K]) Name() Name

Name returns the bound lockout family name.

func (Throttle[K]) Policy

func (t Throttle[K]) Policy() Policy

Policy returns the bound declaration's policy.

func (Throttle[K]) Reset

func (t Throttle[K]) Reset(ctx context.Context, key K) (bool, error)

Reset is an explicit administrative/account-recovery operation. Authorize its caller. It invalidates outstanding attempts; ordinary login success uses the revision check in Run instead. It does not alter request-rate-limit quotas. A DefineLogin throttle clears the account ceiling and the pair for the current request's client; other clients' pair windows expire on their own.

func (Throttle[K]) Run

func (t Throttle[K]) Run(ctx context.Context, key K, verify func(context.Context) (bool, error)) (bool, error)

Run verifies one credential attempt. False,nil records a failure; true,nil conditionally clears old failures. An error or abnormal callback exit records neither and returns no authority. Backend uncertainty never falls back or retries, and a late lock/expiry prevents a successful callback from succeeding. A pending-MFA password proof verifies only that password stage; use a separate declaration for MFA attempts so success here cannot clear MFA failures.

func (Throttle[K]) Validate

func (t Throttle[K]) Validate() error

Validate checks the store binding and declaration.

func (Throttle[K]) WithLockedObserver

func (t Throttle[K]) WithLockedObserver(observer func(context.Context, Notice[K]) error) (Throttle[K], error)

WithLockedObserver returns an immutable view. One confirmed threshold transition calls it once; already-locked denials do not. Lost backend replies can lose the notification. Errors cannot undo lockout and remain causes of its rejection.

Directories

Path Synopsis
Package memory supplies an explicit single-process lockout authority.
Package memory supplies an explicit single-process lockout authority.

Jump to

Keyboard shortcuts

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