Skip to content

Commit fc6048c

Browse files
Marco NapettiTurbo87
Marco Napetti
authored andcommitted
Introduce the concept of daily limit
1 parent 2b6ed6f commit fc6048c

File tree

6 files changed

+671
-3
lines changed

6 files changed

+671
-3
lines changed

src/controllers/krate/publish.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ pub fn publish(req: &mut dyn RequestExt) -> EndpointResult {
128128
)));
129129
}
130130

131+
app.config
132+
.publish_rate_limit
133+
.check_daily_limit(krate.id, &conn)?;
134+
131135
// Length of the .crate tarball, which appears after the metadata in the request body.
132136
// TODO: Not sure why we're using the total content length (metadata + .crate file length)
133137
// to compare against the max upload size... investigate that and perhaps change to use

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ pub mod email;
4646
pub mod github;
4747
pub mod metrics;
4848
pub mod middleware;
49-
mod publish_rate_limit;
49+
pub mod publish_rate_limit;
5050
pub mod schema;
5151
pub mod sql;
5252
mod test_util;

src/publish_rate_limit.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ use std::time::Duration;
66

77
use crate::schema::{publish_limit_buckets, publish_rate_overrides};
88
use crate::sql::{date_part, floor, greatest, interval_part, least};
9-
use crate::util::errors::{AppResult, TooManyRequests};
9+
use crate::util::errors::{cargo_err, AppResult, TooManyRequests};
1010

1111
#[derive(Debug, Clone, Copy)]
1212
pub struct PublishRateLimit {
1313
pub rate: Duration,
1414
pub burst: i32,
15+
pub daily: Option<i64>,
1516
}
1617

1718
impl Default for PublishRateLimit {
@@ -26,9 +27,14 @@ impl Default for PublishRateLimit {
2627
.parse()
2728
.ok()
2829
.unwrap_or(5);
30+
let daily = dotenv::var("MAX_NEW_VERSIONS_DAILY")
31+
.unwrap_or_default()
32+
.parse()
33+
.ok();
2934
Self {
3035
rate: Duration::from_secs(60) * minutes,
3136
burst,
37+
daily,
3238
}
3339
}
3440
}
@@ -106,6 +112,27 @@ impl PublishRateLimit {
106112
use diesel::dsl::*;
107113
(self.rate.as_millis() as i64).milliseconds()
108114
}
115+
116+
pub fn check_daily_limit(&self, krate_id: i32, conn: &PgConnection) -> AppResult<()> {
117+
use crate::schema::versions::dsl::*;
118+
use diesel::dsl::{count_star, now, IntervalDsl};
119+
120+
if let Some(daily_limit) = self.daily {
121+
let today: i64 = versions
122+
.filter(crate_id.eq(krate_id))
123+
.filter(created_at.gt(now - 24.hours()))
124+
.select(count_star())
125+
.first(conn)
126+
.optional()?
127+
.unwrap_or_default();
128+
if today >= daily_limit {
129+
return Err(cargo_err(
130+
"You have published too many versions of this crate in the last 24 hours",
131+
));
132+
}
133+
}
134+
Ok(())
135+
}
109136
}
110137

111138
#[cfg(test)]
@@ -122,6 +149,7 @@ mod tests {
122149
let rate = PublishRateLimit {
123150
rate: Duration::from_secs(1),
124151
burst: 10,
152+
daily: None,
125153
};
126154
let bucket = rate.take_token(new_user(&conn, "user1")?, now, &conn)?;
127155
let expected = Bucket {
@@ -134,6 +162,7 @@ mod tests {
134162
let rate = PublishRateLimit {
135163
rate: Duration::from_millis(50),
136164
burst: 20,
165+
daily: None,
137166
};
138167
let bucket = rate.take_token(new_user(&conn, "user2")?, now, &conn)?;
139168
let expected = Bucket {
@@ -153,6 +182,7 @@ mod tests {
153182
let rate = PublishRateLimit {
154183
rate: Duration::from_secs(1),
155184
burst: 10,
185+
daily: None,
156186
};
157187
let user_id = new_user_bucket(&conn, 5, now)?.user_id;
158188
let bucket = rate.take_token(user_id, now, &conn)?;
@@ -173,6 +203,7 @@ mod tests {
173203
let rate = PublishRateLimit {
174204
rate: Duration::from_secs(1),
175205
burst: 10,
206+
daily: None,
176207
};
177208
let user_id = new_user_bucket(&conn, 5, now)?.user_id;
178209
let refill_time = now + chrono::Duration::seconds(2);
@@ -198,6 +229,7 @@ mod tests {
198229
let rate = PublishRateLimit {
199230
rate: Duration::from_millis(100),
200231
burst: 10,
232+
daily: None,
201233
};
202234
let user_id = new_user_bucket(&conn, 5, now)?.user_id;
203235
let refill_time = now + chrono::Duration::milliseconds(300);
@@ -219,6 +251,7 @@ mod tests {
219251
let rate = PublishRateLimit {
220252
rate: Duration::from_millis(100),
221253
burst: 10,
254+
daily: None,
222255
};
223256
let user_id = new_user_bucket(&conn, 5, now)?.user_id;
224257
let bucket = rate.take_token(user_id, now + chrono::Duration::milliseconds(250), &conn)?;
@@ -240,6 +273,7 @@ mod tests {
240273
let rate = PublishRateLimit {
241274
rate: Duration::from_secs(1),
242275
burst: 10,
276+
daily: None,
243277
};
244278
let user_id = new_user_bucket(&conn, 1, now)?.user_id;
245279
let bucket = rate.take_token(user_id, now, &conn)?;
@@ -263,6 +297,7 @@ mod tests {
263297
let rate = PublishRateLimit {
264298
rate: Duration::from_secs(1),
265299
burst: 10,
300+
daily: None,
266301
};
267302
let user_id = new_user_bucket(&conn, 0, now)?.user_id;
268303
let refill_time = now + chrono::Duration::seconds(1);
@@ -285,6 +320,7 @@ mod tests {
285320
let rate = PublishRateLimit {
286321
rate: Duration::from_secs(1),
287322
burst: 10,
323+
daily: None,
288324
};
289325
let user_id = new_user_bucket(&conn, 8, now)?.user_id;
290326
let refill_time = now + chrono::Duration::seconds(4);
@@ -307,6 +343,7 @@ mod tests {
307343
let rate = PublishRateLimit {
308344
rate: Duration::from_secs(1),
309345
burst: 10,
346+
daily: None,
310347
};
311348
let user_id = new_user(&conn, "user1")?;
312349
let other_user_id = new_user(&conn, "user2")?;
@@ -334,6 +371,7 @@ mod tests {
334371
let rate = PublishRateLimit {
335372
rate: Duration::from_secs(1),
336373
burst: 10,
374+
daily: None,
337375
};
338376
let user_id = new_user(&conn, "user1")?;
339377
let other_user_id = new_user(&conn, "user2")?;

0 commit comments

Comments
 (0)