Skip to content

Commit c02b591

Browse files
author
Marco Napetti
committed
Introduce the concept of daily limit
1 parent 7f0e529 commit c02b591

File tree

8 files changed

+46
-5
lines changed

8 files changed

+46
-5
lines changed

src/config.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ 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;
1718

1819
pub struct Server {
1920
pub base: Base,
@@ -41,6 +42,7 @@ pub struct Server {
4142
pub blocked_routes: HashSet<String>,
4243
pub version_id_cache_size: u64,
4344
pub version_id_cache_ttl: Duration,
45+
pub max_new_versions_daily: i64,
4446
}
4547

4648
impl Default for Server {
@@ -76,6 +78,7 @@ impl Default for Server {
7678
/// endpoint even with a healthy database pool.
7779
/// - `BLOCKED_ROUTES`: A comma separated list of HTTP route patterns that are manually blocked
7880
/// 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.
7982
///
8083
/// # Panics
8184
///
@@ -145,6 +148,8 @@ impl Default for Server {
145148
version_id_cache_ttl: Duration::from_secs(
146149
env_optional("VERSION_ID_CACHE_TTL").unwrap_or(DEFAULT_VERSION_ID_CACHE_TTL),
147150
),
151+
max_new_versions_daily: env_optional("MAX_NEW_VERSIONS_DAILY")
152+
.unwrap_or(DEFAULT_MAX_NEW_VERSIONS_DAILY),
148153
}
149154
}
150155
}

src/controllers/krate/publish.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ pub fn publish(req: &mut dyn RequestExt) -> EndpointResult {
173173
hex_cksum.clone(),
174174
links.clone(),
175175
)?
176-
.save(&conn, &verified_email_address)?;
176+
.save(&conn, &verified_email_address, Some(app.config.max_new_versions_daily))?;
177177

178178
insert_version_owner_action(
179179
&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]")
464+
.save(conn, "[email protected]", None)
465465
.expect("failed to save version");
466466

467467
self.next_version += 1;

src/models/version.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ impl NewVersion {
155155
Ok(new_version)
156156
}
157157

158-
pub fn save(&self, conn: &PgConnection, published_by_email: &str) -> AppResult<Version> {
158+
pub fn save(&self, conn: &PgConnection, published_by_email: &str, daily_limit: Option<i64>) -> AppResult<Version> {
159159
use crate::schema::versions::dsl::*;
160160
use diesel::dsl::exists;
161161
use diesel::{insert_into, select};
@@ -172,6 +172,10 @@ impl NewVersion {
172172
)));
173173
}
174174

175+
if let Some(limit) = daily_limit {
176+
self.rate_limit(conn, limit)?;
177+
}
178+
175179
let version: Version = insert_into(versions).values(self).get_result(conn)?;
176180

177181
insert_into(versions_published_by::table)
@@ -195,6 +199,24 @@ impl NewVersion {
195199
}
196200
Ok(())
197201
}
202+
203+
fn rate_limit(&self, conn: &PgConnection, daily_limit: i64) -> AppResult<()> {
204+
use crate::schema::versions::dsl::*;
205+
use diesel::dsl::{IntervalDsl, count_star, now};
206+
207+
let today: i64 = versions
208+
.filter(crate_id.eq(self.crate_id))
209+
.filter(created_at.gt(now - 24.hours()))
210+
.select(count_star())
211+
.first(conn)
212+
.optional()?
213+
.unwrap_or_default();
214+
if today >= daily_limit {
215+
Err(cargo_err("You have published too many crates in the last 24 hours"))
216+
} else {
217+
Ok(())
218+
}
219+
}
198220
}
199221

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

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]")?;
107+
.save(connection, "[email protected]", None)?;
108108

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

src/tests/util/test_app.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ fn simple_config() -> config::Server {
357357
blocked_routes: HashSet::new(),
358358
version_id_cache_size: 10000,
359359
version_id_cache_ttl: Duration::from_secs(5 * 60),
360+
max_new_versions_daily: 10,
360361
}
361362
}
362363

src/tests/version.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,16 @@ fn version_size() {
177177
.expect("Could not find v2.0.0");
178178
assert_eq!(version2.crate_size, Some(91));
179179
}
180+
181+
#[test]
182+
fn daily_limit() {
183+
let (_, _, user) = TestApp::full().with_user();
184+
185+
for version in 1..=10 {
186+
let crate_to_publish = PublishBuilder::new("foo_version_size").version(&format!("0.0.{}", version));
187+
user.publish_crate(crate_to_publish).good();
188+
}
189+
190+
let crate_to_publish = PublishBuilder::new("foo_version_size").version("1.0.0");
191+
assert!(!user.publish_crate(crate_to_publish).status().is_success());
192+
}

src/worker/update_downloads.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ mod test {
112112
None,
113113
)
114114
.unwrap();
115-
let version = version.save(conn, "[email protected]").unwrap();
115+
let version = version.save(conn, "[email protected]", None).unwrap();
116116
(krate, version)
117117
}
118118

0 commit comments

Comments
 (0)