#[non_exhaustive]pub struct RateLimitConfig {
pub window: Duration,
pub device_user_code_capacity: u64,
pub device_user_code_failure_cost: u64,
pub client_authentication_capacity: u64,
pub client_authentication_capacity_overrides: HashMap<Box<str>, u64>,
pub client_authentication_failure_cost: u64,
pub authorization_request_capacity: u64,
pub authorization_request_failure_cost: u64,
pub client_registration_capacity: u64,
pub client_registration_failure_cost: u64,
pub max_tracked_clients: usize,
}Expand description
The knobs on FixedWindowRateLimiter. RateLimitConfig::default is the reasoned default
set documented at the module level; every field is public so a host can move one without the
builder.
#[non_exhaustive] because later releases will gain budgets for attempt kinds
Attempt does not yet have, and adding one must not break a host that built this by hand.
Fields (Non-exhaustive)§
This struct is marked as non-exhaustive
Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.window: DurationHow long a budget lasts. Clamped up to MIN_WINDOW at use.
device_user_code_capacity: u64Cost units per window for Attempt::DeviceUserCodeEntry, globally.
device_user_code_failure_cost: u64Extra cost charged when a user code entry FAILS.
client_authentication_capacity: u64Cost units per window for Attempt::ClientAuthentication, per client_id.
A RESOURCE SERVER’S INTROSPECTION TRAFFIC IS CHARGED HERE, which is why this number needs
rereading in any deployment that sets crate::server::ServerConfig::resource_servers: an
RFC 7662 introspection is one client authentication, and a resource server makes one per
protected API call. Raise it for that ONE registration with
RateLimitConfig::with_client_authentication_capacity_for rather than for everybody. See
“Resource servers introspect once per API call” in the module docs.
client_authentication_capacity_overrides: HashMap<Box<str>, u64>Per-client_id exceptions to client_authentication_capacity, for registrations whose
honest traffic is not shaped like a client’s.
EMPTY BY DEFAULT and empty means “no exceptions”: a HashMap that has never had an entry
inserted allocates nothing, and the lookup is skipped entirely when it is empty, so a
deployment that does not use this pays one is_empty per check.
The reserve moves with the exception. Everything
CLIENT_AUTHENTICATION_FAILURE_CEILING_DIVISOR guarantees is derived from whichever
capacity applies to the identifier being charged, so a raised registration gets a raised
reserve rather than a failure penalty that saturates after a fiftieth of its budget.
TWO THINGS IT DOES NOT DO, both of them properties of FixedWindowRateLimiter’s bound
rather than of this field. An entry for an identifier longer than
MAX_TRACKED_CLIENT_ID_LEN never applies, because such an identifier never gets a counter
of its own. And an entry does not apply in a window where the tracked map was already full
when this identifier first arrived: it shares the OVERFLOW counter then, on the shared
capacity, because a budget several identifiers share cannot carry one identifier’s
exception. Both degrade toward the ordinary capacity, never away from it.
client_authentication_failure_cost: u64Extra cost charged when a client authentication FAILS.
Cost units per window for Attempt::AuthorizationRequest, per client_id.
Extra cost charged when an authorization request is REFUSED.
client_registration_capacity: u64Cost units per window for Attempt::ClientRegistration, globally.
client_registration_failure_cost: u64Extra cost charged when a dynamic registration is REFUSED.
max_tracked_clients: usizeHow many distinct client_id values get their own counter within a window. See
FixedWindowRateLimiter for what happens past it.
Implementations§
Source§impl RateLimitConfig
impl RateLimitConfig
Sourcepub fn with_window(self, window: Duration) -> Self
pub fn with_window(self, window: Duration) -> Self
Set the window, clamped up to MIN_WINDOW.
Sourcepub fn with_device_user_code_budget(
self,
capacity: u64,
failure_cost: u64,
) -> Self
pub fn with_device_user_code_budget( self, capacity: u64, failure_cost: u64, ) -> Self
Set the Attempt::DeviceUserCodeEntry budget: capacity cost units per window, with
failure_cost charged on top of ATTEMPT_COST for each failure.
A capacity of 0 refuses every user code entry, which is a legitimate way to turn the verification endpoint off; it is not treated as “unlimited”.
Sourcepub fn with_client_authentication_budget(
self,
capacity: u64,
failure_cost: u64,
) -> Self
pub fn with_client_authentication_budget( self, capacity: u64, failure_cost: u64, ) -> Self
Set the per-client_id Attempt::ClientAuthentication budget, in the same units as
RateLimitConfig::with_device_user_code_budget.
Sourcepub fn with_client_authentication_capacity_for(
self,
client_id: impl Into<Box<str>>,
capacity: u64,
) -> Self
pub fn with_client_authentication_capacity_for( self, client_id: impl Into<Box<str>>, capacity: u64, ) -> Self
Give ONE client_id its own Attempt::ClientAuthentication capacity, leaving every other
registration on client_authentication_capacity.
WHAT THIS IS FOR, and it is one thing: a registration whose honest volume is a function of
somebody else’s traffic rather than of its own. The case that exists today is a RESOURCE
SERVER declared in crate::server::ServerConfig::resource_servers, which authenticates
here once per RFC 7662 introspection and therefore once per call at the protected resource
it guards — a rate set by that API’s clients, not by any grant this server issued.
It exists so that the sizing advice can be given about one registration. Raising
client_authentication_capacity globally would raise it for every client id an attacker
can name, and the per-client ceiling is what bounds how many WRONG SECRETS one id can push
through the host’s secret verifier in a window: at the defaults 3000, and each one may cost
an argon2id. A twentyfold global raise is a twentyfold raise in that, for every
registration, to buy headroom one of them needed.
use oauth_as::rate_limit::{FixedWindowRateLimiter, RateLimitConfig};
// 100 API calls a second at the protected resource is 6000 introspections a minute, which
// is the whole default budget. Give that one registration room and leave the rest alone.
let limiter = FixedWindowRateLimiter::with_config(
RateLimitConfig::default().with_client_authentication_capacity_for("orders-api", 120_000),
);A capacity of 0 refuses that identifier outright, which is a legitimate way to take one registration off the air; it is not read as “unlimited”.
Sourcepub fn with_max_tracked_clients(self, max: usize) -> Self
pub fn with_max_tracked_clients(self, max: usize) -> Self
Set how many distinct client_id values get their own counter within a window.
Trait Implementations§
Source§impl Clone for RateLimitConfig
impl Clone for RateLimitConfig
Source§fn clone(&self) -> RateLimitConfig
fn clone(&self) -> RateLimitConfig
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more