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
- func ValidateOperation(ctx context.Context, key Key, policy Policy) error
- type Admission
- type Backend
- type Code
- type Config
- type Decision
- type Declaration
- type Generation
- type Key
- type Limits
- type Name
- type Notice
- type Outcome
- type Policy
- type Rejection
- type Snapshot
- type Status
- type Store
- type Throttle
- func (t Throttle[K]) Limits() (Limits, bool)
- func (t Throttle[K]) Name() Name
- func (t Throttle[K]) Policy() Policy
- func (t Throttle[K]) Reset(ctx context.Context, key K) (bool, error)
- func (t Throttle[K]) Run(ctx context.Context, key K, verify func(context.Context) (bool, error)) (bool, error)
- func (t Throttle[K]) Validate() error
- func (t Throttle[K]) WithLockedObserver(observer func(context.Context, Notice[K]) error) (Throttle[K], error)
Constants ¶
const KeyFeature keyspace.Feature = "lockout"
KeyFeature is the physical address family owned by this adapter.
const MaxDuration = ratelimit.MaxWindow
const MaxFailures uint32 = 10000
Variables ¶
This section is empty.
Functions ¶
Types ¶
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 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 ¶
DefaultConfig returns default key, declaration, concurrency and timeout bounds for namespace.
type Decision ¶
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.
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 ¶
Define declares a single-key throttle, for example MFA attempts keyed by an already authenticated subject. Submitted login identifiers use DefineLogin.
func DefineLogin ¶
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 ¶
NewKey validates and constructs an adapter address for namespace, name and logical key.
type Limits ¶
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.
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 ¶
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 ¶
RetryAfter returns the lock duration recorded in the notice.
type Outcome ¶
type Outcome uint8
Outcome identifies whether an admitted credential attempt failed or succeeded.
type Policy ¶
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.
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) RetryAfter ¶
RetryAfter returns the remaining lock duration, or zero 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.
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.
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 ¶
Limits returns the client-aware limits when this throttle came from DefineLogin.
func (Throttle[K]) Reset ¶
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]) 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.