Skip to main content

RateLimitConfig

Struct RateLimitConfig 

Source
#[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
Non-exhaustive structs could have additional fields added in future. Therefore, non-exhaustive structs cannot be constructed in external crates using the traditional Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.
§window: Duration

How long a budget lasts. Clamped up to MIN_WINDOW at use.

§device_user_code_capacity: u64

Cost units per window for Attempt::DeviceUserCodeEntry, globally.

§device_user_code_failure_cost: u64

Extra cost charged when a user code entry FAILS.

§client_authentication_capacity: u64

Cost 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: u64

Extra cost charged when a client authentication FAILS.

§authorization_request_capacity: u64

Cost units per window for Attempt::AuthorizationRequest, per client_id.

§authorization_request_failure_cost: u64

Extra cost charged when an authorization request is REFUSED.

§client_registration_capacity: u64

Cost units per window for Attempt::ClientRegistration, globally.

§client_registration_failure_cost: u64

Extra cost charged when a dynamic registration is REFUSED.

§max_tracked_clients: usize

How many distinct client_id values get their own counter within a window. See FixedWindowRateLimiter for what happens past it.

Implementations§

Source§

impl RateLimitConfig

Source

pub fn with_window(self, window: Duration) -> Self

Set the window, clamped up to MIN_WINDOW.

Source

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”.

Source

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.

Source

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”.

Source

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

Source§

fn clone(&self) -> RateLimitConfig

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for RateLimitConfig

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for RateLimitConfig

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl Eq for RateLimitConfig

Source§

impl PartialEq for RateLimitConfig

Source§

fn eq(&self, other: &RateLimitConfig) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for RateLimitConfig

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.