Skip to content

Commit 4358690

Browse files
committed
move existing crate update rate limit to the application
1 parent 6f80723 commit 4358690

File tree

9 files changed

+91
-29
lines changed

9 files changed

+91
-29
lines changed

config/nginx.conf.erb

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,6 @@ http {
185185
client_body_timeout 30;
186186
client_max_body_size 50m;
187187

188-
limit_req_zone $remote_addr zone=publish:10m rate=1r/m;
189-
190188
upstream app_server {
191189
server localhost:8888 fail_timeout=0;
192190
}
@@ -262,12 +260,5 @@ http {
262260
proxy_pass http://app_server;
263261
}
264262
<% end %>
265-
266-
location ~ ^/api/v./crates/new$ {
267-
proxy_pass http://app_server;
268-
269-
limit_req zone=publish burst=30 nodelay;
270-
limit_req_status 429;
271-
}
272263
}
273264
}

src/controllers/krate/publish.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use crate::models::{
1919

2020
use crate::middleware::log_request::RequestLogExt;
2121
use crate::models::token::EndpointScope;
22+
use crate::rate_limiter::LimitedAction;
2223
use crate::schema::*;
2324
use crate::util::errors::{cargo_err, internal, AppResult};
2425
use crate::util::Maximums;
@@ -101,6 +102,14 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
101102
))
102103
})?;
103104

105+
// Use a different rate limit whether this is a new or an existing crate.
106+
let rate_limit_action = match existing_crate {
107+
Some(_) => LimitedAction::PublishUpdate,
108+
None => LimitedAction::PublishNew,
109+
};
110+
app.rate_limiter
111+
.check_rate_limit(user.id, rate_limit_action, conn)?;
112+
104113
// Create a transaction on the database, if there are no errors,
105114
// commit the transactions to record a new or updated crate.
106115
conn.transaction(|conn| {
@@ -137,7 +146,7 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
137146
};
138147

139148
let license_file = new_crate.license_file.as_deref();
140-
let krate = persist.create_or_update(conn, user.id, Some(&app.rate_limiter))?;
149+
let krate = persist.create_or_update(conn, user.id)?;
141150

142151
let owners = krate.owners(conn)?;
143152
if user.rights(&app, &owners)? < Rights::Publish {

src/downloads_counter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ mod tests {
439439
name: "foo",
440440
..NewCrate::default()
441441
}
442-
.create_or_update(conn, user.id, None)
442+
.create_or_update(conn, user.id)
443443
.expect("failed to create crate");
444444

445445
Self {

src/models/krate.rs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ use crate::models::{
1717
use crate::util::errors::{cargo_err, AppResult};
1818

1919
use crate::models::helpers::with_count::*;
20-
use crate::rate_limiter::{LimitedAction, RateLimiter};
2120
use crate::schema::*;
2221
use crate::sql::canon_crate_name;
2322

@@ -103,12 +102,7 @@ pub struct NewCrate<'a> {
103102
}
104103

105104
impl<'a> NewCrate<'a> {
106-
pub fn create_or_update(
107-
self,
108-
conn: &mut PgConnection,
109-
uploader: i32,
110-
rate_limit: Option<&RateLimiter>,
111-
) -> AppResult<Crate> {
105+
pub fn create_or_update(self, conn: &mut PgConnection, uploader: i32) -> AppResult<Crate> {
112106
use diesel::update;
113107

114108
self.validate()?;
@@ -118,9 +112,6 @@ impl<'a> NewCrate<'a> {
118112
// To avoid race conditions, we try to insert
119113
// first so we know whether to add an owner
120114
if let Some(krate) = self.save_new_crate(conn, uploader)? {
121-
if let Some(rate_limit) = rate_limit {
122-
rate_limit.check_rate_limit(uploader, LimitedAction::PublishNew, conn)?;
123-
}
124115
return Ok(krate);
125116
}
126117

src/rate_limiter.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,29 @@ use std::time::Duration;
1212
pg_enum! {
1313
pub enum LimitedAction {
1414
PublishNew = 0,
15+
PublishUpdate = 1,
1516
}
1617
}
1718

1819
impl LimitedAction {
1920
pub fn default_rate_seconds(&self) -> u64 {
2021
match self {
2122
LimitedAction::PublishNew => 60 * 60,
23+
LimitedAction::PublishUpdate => 60,
2224
}
2325
}
2426

2527
pub fn default_burst(&self) -> i32 {
2628
match self {
2729
LimitedAction::PublishNew => 5,
30+
LimitedAction::PublishUpdate => 30,
2831
}
2932
}
3033

3134
pub fn env_var_key(&self) -> &'static str {
3235
match self {
3336
LimitedAction::PublishNew => "PUBLISH_NEW",
37+
LimitedAction::PublishUpdate => "PUBLISH_EXISTING",
3438
}
3539
}
3640

@@ -39,6 +43,9 @@ impl LimitedAction {
3943
LimitedAction::PublishNew => {
4044
"You have published too many new crates in a short period of time"
4145
}
46+
LimitedAction::PublishUpdate => {
47+
"You have published too many updates to existing crates in a short period of time"
48+
}
4249
}
4350
}
4451
}

src/tests/builders/krate.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,7 @@ impl<'a> CrateBuilder<'a> {
114114
pub fn build(mut self, connection: &mut PgConnection) -> AppResult<Crate> {
115115
use diesel::{insert_into, select, update};
116116

117-
let mut krate = self
118-
.krate
119-
.create_or_update(connection, self.owner_id, None)?;
117+
let mut krate = self.krate.create_or_update(connection, self.owner_id)?;
120118

121119
// Since we are using `NewCrate`, we can't set all the
122120
// crate properties in a single DB call.

src/tests/krate/publish.rs

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,8 +1028,7 @@ fn publish_new_crate_rate_limited() {
10281028

10291029
// Uploading a second crate is limited
10301030
let crate_to_publish = PublishBuilder::new("rate_limited2");
1031-
let response = token.publish_crate(crate_to_publish);
1032-
assert_eq!(response.status(), StatusCode::TOO_MANY_REQUESTS);
1031+
token.publish_crate(crate_to_publish).assert_rate_limited();
10331032

10341033
assert_eq!(app.stored_files().len(), 2);
10351034

@@ -1049,9 +1048,9 @@ fn publish_new_crate_rate_limited() {
10491048
}
10501049

10511050
#[test]
1052-
fn publish_rate_limit_doesnt_affect_existing_crates() {
1051+
fn publish_new_crate_rate_limit_doesnt_affect_existing_crates() {
10531052
let (_, _, _, token) = TestApp::full()
1054-
.with_rate_limit(LimitedAction::PublishNew, Duration::from_millis(500), 1)
1053+
.with_rate_limit(LimitedAction::PublishNew, Duration::from_secs(60 * 60), 1)
10551054
.with_token();
10561055

10571056
// Upload a new crate
@@ -1062,6 +1061,67 @@ fn publish_rate_limit_doesnt_affect_existing_crates() {
10621061
token.publish_crate(new_version).good();
10631062
}
10641063

1064+
#[test]
1065+
fn publish_existing_crate_rate_limited() {
1066+
let (app, anon, _, token) = TestApp::full()
1067+
.with_rate_limit(LimitedAction::PublishUpdate, Duration::from_millis(500), 1)
1068+
.with_token();
1069+
1070+
// Upload a new crate
1071+
let crate_to_publish = PublishBuilder::new("rate_limited1");
1072+
token.publish_crate(crate_to_publish).good();
1073+
1074+
let json = anon.show_crate("rate_limited1");
1075+
assert_eq!(json.krate.max_version, "1.0.0");
1076+
assert_eq!(app.stored_files().len(), 2);
1077+
1078+
// Uploading the first update to the crate works
1079+
let crate_to_publish = PublishBuilder::new("rate_limited1").version("1.0.1");
1080+
token.publish_crate(crate_to_publish).good();
1081+
1082+
let json = anon.show_crate("rate_limited1");
1083+
assert_eq!(json.krate.max_version, "1.0.1");
1084+
assert_eq!(app.stored_files().len(), 3);
1085+
1086+
// Uploading the second update to the crate is rate limited
1087+
let crate_to_publish = PublishBuilder::new("rate_limited1").version("1.0.2");
1088+
token.publish_crate(crate_to_publish).assert_rate_limited();
1089+
1090+
// Check that version 1.0.2 was not published
1091+
let json = anon.show_crate("rate_limited1");
1092+
assert_eq!(json.krate.max_version, "1.0.1");
1093+
assert_eq!(app.stored_files().len(), 3);
1094+
1095+
// Wait for the limit to be up
1096+
thread::sleep(Duration::from_millis(500));
1097+
1098+
let crate_to_publish = PublishBuilder::new("rate_limited1").version("1.0.2");
1099+
token.publish_crate(crate_to_publish).good();
1100+
1101+
let json = anon.show_crate("rate_limited1");
1102+
assert_eq!(json.krate.max_version, "1.0.2");
1103+
assert_eq!(app.stored_files().len(), 4);
1104+
}
1105+
1106+
#[test]
1107+
fn publish_existing_crate_rate_limit_doesnt_affect_new_crates() {
1108+
let (_, _, _, token) = TestApp::full()
1109+
.with_rate_limit(
1110+
LimitedAction::PublishUpdate,
1111+
Duration::from_secs(60 * 60),
1112+
1,
1113+
)
1114+
.with_token();
1115+
1116+
// Upload a new crate
1117+
let crate_to_publish = PublishBuilder::new("rate_limited1");
1118+
token.publish_crate(crate_to_publish).good();
1119+
1120+
// Upload a second new crate
1121+
let crate_to_publish = PublishBuilder::new("rate_limited2");
1122+
token.publish_crate(crate_to_publish).good();
1123+
}
1124+
10651125
#[test]
10661126
fn features_version_2() {
10671127
let (app, _, user, token) = TestApp::full().with_token();

src/tests/util/response.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ impl<T> Response<T> {
5757
.ends_with(target));
5858
self
5959
}
60+
61+
/// Assert that the status code is 429
62+
#[track_caller]
63+
pub fn assert_rate_limited(&self) {
64+
assert_eq!(StatusCode::TOO_MANY_REQUESTS, self.status());
65+
}
6066
}
6167

6268
impl Response<()> {

src/worker/update_downloads.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ mod test {
9898
name: "foo",
9999
..Default::default()
100100
}
101-
.create_or_update(conn, user_id, None)
101+
.create_or_update(conn, user_id)
102102
.unwrap();
103103
let version = NewVersion::new(
104104
krate.id,

0 commit comments

Comments
 (0)