Skip to content

Commit 99d8b60

Browse files
committed
move existing crate update rate limit to the application
1 parent 2d8cf4a commit 99d8b60

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
@@ -1016,8 +1016,7 @@ fn publish_new_crate_rate_limited() {
10161016

10171017
// Uploading a second crate is limited
10181018
let crate_to_publish = PublishBuilder::new("rate_limited2", "1.0.0");
1019-
let response = token.publish_crate(crate_to_publish);
1020-
assert_eq!(response.status(), StatusCode::TOO_MANY_REQUESTS);
1019+
token.publish_crate(crate_to_publish).assert_rate_limited();
10211020

10221021
assert_eq!(app.stored_files().len(), 2);
10231022

@@ -1037,9 +1036,9 @@ fn publish_new_crate_rate_limited() {
10371036
}
10381037

10391038
#[test]
1040-
fn publish_rate_limit_doesnt_affect_existing_crates() {
1039+
fn publish_new_crate_rate_limit_doesnt_affect_existing_crates() {
10411040
let (_, _, _, token) = TestApp::full()
1042-
.with_rate_limit(LimitedAction::PublishNew, Duration::from_millis(500), 1)
1041+
.with_rate_limit(LimitedAction::PublishNew, Duration::from_secs(60 * 60), 1)
10431042
.with_token();
10441043

10451044
// Upload a new crate
@@ -1050,6 +1049,67 @@ fn publish_rate_limit_doesnt_affect_existing_crates() {
10501049
token.publish_crate(new_version).good();
10511050
}
10521051

1052+
#[test]
1053+
fn publish_existing_crate_rate_limited() {
1054+
let (app, anon, _, token) = TestApp::full()
1055+
.with_rate_limit(LimitedAction::PublishUpdate, Duration::from_millis(500), 1)
1056+
.with_token();
1057+
1058+
// Upload a new crate
1059+
let crate_to_publish = PublishBuilder::new("rate_limited1");
1060+
token.publish_crate(crate_to_publish).good();
1061+
1062+
let json = anon.show_crate("rate_limited1");
1063+
assert_eq!(json.krate.max_version, "1.0.0");
1064+
assert_eq!(app.stored_files().len(), 2);
1065+
1066+
// Uploading the first update to the crate works
1067+
let crate_to_publish = PublishBuilder::new("rate_limited1").version("1.0.1");
1068+
token.publish_crate(crate_to_publish).good();
1069+
1070+
let json = anon.show_crate("rate_limited1");
1071+
assert_eq!(json.krate.max_version, "1.0.1");
1072+
assert_eq!(app.stored_files().len(), 3);
1073+
1074+
// Uploading the second update to the crate is rate limited
1075+
let crate_to_publish = PublishBuilder::new("rate_limited1").version("1.0.2");
1076+
token.publish_crate(crate_to_publish).assert_rate_limited();
1077+
1078+
// Check that version 1.0.2 was not published
1079+
let json = anon.show_crate("rate_limited1");
1080+
assert_eq!(json.krate.max_version, "1.0.1");
1081+
assert_eq!(app.stored_files().len(), 3);
1082+
1083+
// Wait for the limit to be up
1084+
thread::sleep(Duration::from_millis(500));
1085+
1086+
let crate_to_publish = PublishBuilder::new("rate_limited1").version("1.0.2");
1087+
token.publish_crate(crate_to_publish).good();
1088+
1089+
let json = anon.show_crate("rate_limited1");
1090+
assert_eq!(json.krate.max_version, "1.0.2");
1091+
assert_eq!(app.stored_files().len(), 4);
1092+
}
1093+
1094+
#[test]
1095+
fn publish_existing_crate_rate_limit_doesnt_affect_new_crates() {
1096+
let (_, _, _, token) = TestApp::full()
1097+
.with_rate_limit(
1098+
LimitedAction::PublishUpdate,
1099+
Duration::from_secs(60 * 60),
1100+
1,
1101+
)
1102+
.with_token();
1103+
1104+
// Upload a new crate
1105+
let crate_to_publish = PublishBuilder::new("rate_limited1");
1106+
token.publish_crate(crate_to_publish).good();
1107+
1108+
// Upload a second new crate
1109+
let crate_to_publish = PublishBuilder::new("rate_limited2");
1110+
token.publish_crate(crate_to_publish).good();
1111+
}
1112+
10531113
#[test]
10541114
fn features_version_2() {
10551115
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)