Skip to content

Commit f5b14b8

Browse files
author
Marco Napetti
committed
move check_daily_limit inside PushRateLimit
1 parent d291500 commit f5b14b8

File tree

11 files changed

+663
-48
lines changed

11 files changed

+663
-48
lines changed

src/config.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ use std::time::Duration;
1414

1515
const DEFAULT_VERSION_ID_CACHE_SIZE: u64 = 10_000;
1616
const DEFAULT_VERSION_ID_CACHE_TTL: u64 = 5 * 60; // 5 minutes
17-
const DEFAULT_MAX_NEW_VERSIONS_DAILY: i64 = 10;
1817

1918
pub struct Server {
2019
pub base: Base,
@@ -42,7 +41,6 @@ pub struct Server {
4241
pub blocked_routes: HashSet<String>,
4342
pub version_id_cache_size: u64,
4443
pub version_id_cache_ttl: Duration,
45-
pub max_new_versions_daily: i64,
4644
}
4745

4846
impl Default for Server {
@@ -78,7 +76,6 @@ impl Default for Server {
7876
/// endpoint even with a healthy database pool.
7977
/// - `BLOCKED_ROUTES`: A comma separated list of HTTP route patterns that are manually blocked
8078
/// by an operator (e.g. `/crates/:crate_id/:version/download`).
81-
/// - `MAX_NEW_VERSIONS_DAILY`: Maximum number of new crate versions creatable in a 24 hours span.
8279
///
8380
/// # Panics
8481
///
@@ -148,8 +145,6 @@ impl Default for Server {
148145
version_id_cache_ttl: Duration::from_secs(
149146
env_optional("VERSION_ID_CACHE_TTL").unwrap_or(DEFAULT_VERSION_ID_CACHE_TTL),
150147
),
151-
max_new_versions_daily: env_optional("MAX_NEW_VERSIONS_DAILY")
152-
.unwrap_or(DEFAULT_MAX_NEW_VERSIONS_DAILY),
153148
}
154149
}
155150
}

src/controllers/krate/publish.rs

Lines changed: 5 additions & 5 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
@@ -173,11 +177,7 @@ pub fn publish(req: &mut dyn RequestExt) -> EndpointResult {
173177
hex_cksum.clone(),
174178
links.clone(),
175179
)?
176-
.save(
177-
&conn,
178-
&verified_email_address,
179-
Some(app.config.max_new_versions_daily),
180-
)?;
180+
.save(&conn, &verified_email_address)?;
181181

182182
insert_version_owner_action(
183183
&conn,

src/downloads_counter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ mod tests {
461461
None,
462462
)
463463
.expect("failed to create version")
464-
.save(conn, "[email protected]", None)
464+
.save(conn, "[email protected]")
465465
.expect("failed to save version");
466466

467467
self.next_version += 1;

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/models/version.rs

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,7 @@ impl NewVersion {
155155
Ok(new_version)
156156
}
157157

158-
pub fn save(
159-
&self,
160-
conn: &PgConnection,
161-
published_by_email: &str,
162-
daily_limit: Option<i64>,
163-
) -> AppResult<Version> {
158+
pub fn save(&self, conn: &PgConnection, published_by_email: &str) -> AppResult<Version> {
164159
use crate::schema::versions::dsl::*;
165160
use diesel::dsl::exists;
166161
use diesel::{insert_into, select};
@@ -177,10 +172,6 @@ impl NewVersion {
177172
)));
178173
}
179174

180-
if let Some(limit) = daily_limit {
181-
self.rate_limit(conn, limit)?;
182-
}
183-
184175
let version: Version = insert_into(versions).values(self).get_result(conn)?;
185176

186177
insert_into(versions_published_by::table)
@@ -204,26 +195,6 @@ impl NewVersion {
204195
}
205196
Ok(())
206197
}
207-
208-
fn rate_limit(&self, conn: &PgConnection, daily_limit: i64) -> AppResult<()> {
209-
use crate::schema::versions::dsl::*;
210-
use diesel::dsl::{count_star, now, IntervalDsl};
211-
212-
let today: i64 = versions
213-
.filter(crate_id.eq(self.crate_id))
214-
.filter(created_at.gt(now - 24.hours()))
215-
.select(count_star())
216-
.first(conn)
217-
.optional()?
218-
.unwrap_or_default();
219-
if today >= daily_limit {
220-
Err(cargo_err(
221-
"You have published too many crates in the last 24 hours",
222-
))
223-
} else {
224-
Ok(())
225-
}
226-
}
227198
}
228199

229200
fn validate_license_expr(s: &str) -> AppResult<()> {

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 crates 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")?;

src/tests/builders/version.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ impl<'a> VersionBuilder<'a> {
104104
self.checksum,
105105
self.links,
106106
)?
107-
.save(connection, "[email protected]", None)?;
107+
.save(connection, "[email protected]")?;
108108

109109
if self.yanked {
110110
vers = update(&vers)

0 commit comments

Comments
 (0)