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(context.Context, Key, Policy, Generation) (Admission, error)
LockoutFinish(context.Context, Key, Policy, Snapshot, Outcome) (Decision, error)
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 ¶
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]) 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
func (Declaration[K]) Policy ¶
func (d Declaration[K]) Policy() Policy
func (Declaration[K]) Validate ¶
func (d Declaration[K]) Validate() error
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)
func ParseGeneration ¶
func ParseGeneration(text string) (Generation, error)
func (Generation) GoString ¶
func (g Generation) GoString() string
func (Generation) String ¶
func (Generation) String() string
func (Generation) Token ¶
func (g Generation) Token() string
func (Generation) Validate ¶
func (g Generation) Validate() error
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.
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]) RetryAfter ¶
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
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 ¶
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.