Skip to content
This repository was archived by the owner on Sep 10, 2024. It is now read-only.

Commit e25c170

Browse files
committed
Rate-limit password-based login attempts
1 parent f5b4caf commit e25c170

File tree

13 files changed

+525
-15
lines changed

13 files changed

+525
-15
lines changed

Cargo.lock

Lines changed: 92 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ features = ["derive"]
106106
version = "0.10.19"
107107
features = ["env", "yaml", "test"]
108108

109+
# Rate-limiting
110+
[workspace.dependencies.governor]
111+
version = "0.6.3"
112+
109113
# HTTP headers
110114
[workspace.dependencies.headers]
111115
version = "0.4.0"
@@ -164,6 +168,10 @@ features = [
164168
[workspace.dependencies.minijinja]
165169
version = "2.1.0"
166170

171+
# Utilities to deal with non-zero values
172+
[workspace.dependencies.nonzero_ext]
173+
version = "0.3.0"
174+
167175
# Random values
168176
[workspace.dependencies.rand]
169177
version = "0.8.5"

crates/cli/src/app_state.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use ipnetwork::IpNetwork;
2222
use mas_data_model::SiteConfig;
2323
use mas_handlers::{
2424
passwords::PasswordManager, ActivityTracker, BoundActivityTracker, CookieManager, ErrorWrapper,
25-
GraphQLSchema, HttpClientFactory, MetadataCache,
25+
GraphQLSchema, HttpClientFactory, Limiter, MetadataCache, RequesterFingerprint,
2626
};
2727
use mas_i18n::Translator;
2828
use mas_keystore::{Encrypter, Keystore};
@@ -57,6 +57,7 @@ pub struct AppState {
5757
pub site_config: SiteConfig,
5858
pub activity_tracker: ActivityTracker,
5959
pub trusted_proxies: Vec<IpNetwork>,
60+
pub limiter: Limiter,
6061
pub conn_acquisition_histogram: Option<Histogram<u64>>,
6162
}
6263

@@ -210,6 +211,12 @@ impl FromRef<AppState> for SiteConfig {
210211
}
211212
}
212213

214+
impl FromRef<AppState> for Limiter {
215+
fn from_ref(input: &AppState) -> Self {
216+
input.limiter.clone()
217+
}
218+
}
219+
213220
impl FromRef<AppState> for BoxHomeserverConnection {
214221
fn from_ref(input: &AppState) -> Self {
215222
Box::new(input.homeserver_connection.clone())
@@ -326,12 +333,35 @@ impl FromRequestParts<AppState> for BoundActivityTracker {
326333
parts: &mut axum::http::request::Parts,
327334
state: &AppState,
328335
) -> Result<Self, Self::Rejection> {
336+
// TODO: we may infer the IP twice, for the activity tracker and the limiter
329337
let ip = infer_client_ip(parts, &state.trusted_proxies);
330338
tracing::debug!(ip = ?ip, "Inferred client IP address");
331339
Ok(state.activity_tracker.clone().bind(ip))
332340
}
333341
}
334342

343+
#[async_trait]
344+
impl FromRequestParts<AppState> for RequesterFingerprint {
345+
type Rejection = Infallible;
346+
347+
async fn from_request_parts(
348+
parts: &mut axum::http::request::Parts,
349+
state: &AppState,
350+
) -> Result<Self, Self::Rejection> {
351+
// TODO: we may infer the IP twice, for the activity tracker and the limiter
352+
let ip = infer_client_ip(parts, &state.trusted_proxies);
353+
354+
if let Some(ip) = ip {
355+
Ok(RequesterFingerprint::new(ip))
356+
} else {
357+
// If we can't infer the IP address, we'll just use an empty fingerprint and
358+
// warn about it
359+
tracing::warn!("Could not infer client IP address for an operation which rate-limits based on IP addresses");
360+
Ok(RequesterFingerprint::EMPTY)
361+
}
362+
}
363+
}
364+
335365
#[async_trait]
336366
impl FromRequestParts<AppState> for BoxRepository {
337367
type Rejection = ErrorWrapper<mas_storage_pg::DatabaseError>;

crates/cli/src/commands/server.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use clap::Parser;
1919
use figment::Figment;
2020
use itertools::Itertools;
2121
use mas_config::{AppConfig, ClientsConfig, ConfigurationSection, UpstreamOAuth2Config};
22-
use mas_handlers::{ActivityTracker, CookieManager, HttpClientFactory, MetadataCache};
22+
use mas_handlers::{ActivityTracker, CookieManager, HttpClientFactory, Limiter, MetadataCache};
2323
use mas_listener::{server::Server, shutdown::ShutdownStream};
2424
use mas_matrix_synapse::SynapseConnection;
2525
use mas_router::UrlBuilder;
@@ -200,6 +200,8 @@ impl Options {
200200
// Listen for SIGHUP
201201
register_sighup(&templates, &activity_tracker)?;
202202

203+
let limiter = Limiter::default();
204+
203205
let graphql_schema = mas_handlers::graphql_schema(
204206
&pool,
205207
&policy_factory,
@@ -213,7 +215,6 @@ impl Options {
213215
pool,
214216
templates,
215217
key_store,
216-
metadata_cache,
217218
cookie_manager,
218219
encrypter,
219220
url_builder,
@@ -222,9 +223,11 @@ impl Options {
222223
graphql_schema,
223224
http_client_factory,
224225
password_manager,
226+
metadata_cache,
225227
site_config,
226228
activity_tracker,
227229
trusted_proxies,
230+
limiter,
228231
conn_acquisition_histogram: None,
229232
};
230233
s.init_metrics()?;

crates/handlers/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,14 @@ zeroize = "1.8.1"
6767
base64ct = "1.6.0"
6868
camino.workspace = true
6969
chrono.workspace = true
70+
governor.workspace = true
7071
indexmap = "2.2.6"
7172
psl = "2.1.55"
7273
time = "0.3.36"
7374
url.workspace = true
7475
mime = "0.3.17"
7576
minijinja.workspace = true
77+
nonzero_ext.workspace = true
7678
rand.workspace = true
7779
rand_chacha = "0.3.1"
7880
headers.workspace = true

0 commit comments

Comments
 (0)