diff --git a/Cargo.lock b/Cargo.lock index fc4b0ed3..e4876738 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4723,6 +4723,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.1.2", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -6059,6 +6072,7 @@ dependencies = [ "percent-encoding", "pin-project", "prost", + "rustls-native-certs", "rustls-pemfile 2.1.2", "rustls-pki-types", "tokio", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index aee4b774..e9455791 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -31,7 +31,7 @@ ip_network = { version = "0.4.1", features = ["serde"] } opentelemetry = { version = "0.23.0", features = ["metrics"] } opentelemetry_sdk = { version = "0.23.0", features = ["rt-tokio", "metrics"] } opentelemetry-stdout = { version = "0.4.0", features = ["metrics"] } -opentelemetry-otlp = { version = "0.16.0", features = ["metrics"] } +opentelemetry-otlp = { version = "0.16.0", features = ["metrics", "tls-roots"] } opentelemetry-semantic-conventions = "0.16.0" tracing-opentelemetry = { version = "0.24.0", features = ["metrics"] } tracing-core = "0.1.32" diff --git a/backend/justfile b/backend/justfile index 071099f6..96047b08 100644 --- a/backend/justfile +++ b/backend/justfile @@ -8,7 +8,8 @@ entity-codegen: sea-orm-cli migrate -u sqlite://database/backend.sqlite?mode=rwc sea-orm-cli generate entity -u sqlite://database/backend.sqlite?mode=rwc -o src/pending -run: +dev: + just prepare cargo run run-release: @@ -17,3 +18,6 @@ run-release: ci-test: just prepare cargo test + +setup-judger: + cd ../docker/dev && sudo docker compose --profile backend-dev up diff --git a/backend/src/controller/token.rs b/backend/src/controller/token.rs index 958cb158..29fcbbd0 100644 --- a/backend/src/controller/token.rs +++ b/backend/src/controller/token.rs @@ -149,6 +149,7 @@ impl TokenController { token } None => { + // FIXME: this is cold branch! let token: CachedToken = (token::Entity::find() .filter(token::Column::Rand.eq(rand.to_vec())) .one(self.db.deref()) diff --git a/backend/src/endpoint/announcement.rs b/backend/src/endpoint/announcement.rs index 4d14d580..21b952ad 100644 --- a/backend/src/endpoint/announcement.rs +++ b/backend/src/endpoint/announcement.rs @@ -56,13 +56,7 @@ impl Announcement for ArcServer { &self, req: Request, ) -> Result, Status> { - let (auth, req) = self - .parse_request_fn(req, |req| { - (req.size + req.offset.saturating_abs() as u64 / 5 + 2) - .try_into() - .unwrap_or(u32::MAX) - }) - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; req.bound_check()?; @@ -84,8 +78,11 @@ impl Announcement for ArcServer { }; let mut paginator = paginator.with_auth(&auth).with_db(&self.db); - let list = paginator.fetch(req.size, req.offset).await?; - let remain = paginator.remain().await?; + let list = paginator + .fetch(req.size, req.offset) + .in_current_span() + .await?; + let remain = paginator.remain().in_current_span().await?; let paginator = paginator.into_inner(); @@ -97,10 +94,7 @@ impl Announcement for ArcServer { } #[instrument(skip_all, level = "debug")] async fn full_info(&self, req: Request) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; tracing::debug!(announcement_id = req.id); @@ -119,10 +113,7 @@ impl Announcement for ArcServer { &self, req: Request, ) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let (user_id, perm) = auth.auth_or_guest()?; req.bound_check()?; @@ -159,10 +150,7 @@ impl Announcement for ArcServer { &self, req: Request, ) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let (user_id, _perm) = auth.auth_or_guest()?; req.bound_check()?; @@ -196,10 +184,7 @@ impl Announcement for ArcServer { } #[instrument(skip_all, level = "debug")] async fn remove(&self, req: Request) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let result = Entity::write_filter(Entity::delete_by_id(Into::::into(req.id)), &auth)? .exec(self.db.deref()) @@ -220,10 +205,7 @@ impl Announcement for ArcServer { &self, req: Request, ) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let (user_id, perm) = auth.auth_or_guest()?; if !perm.super_user() { @@ -267,11 +249,7 @@ impl Announcement for ArcServer { &self, req: Request, ) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; - + let (auth, req) = self.rate_limit(req).in_current_span().await?; let mut announcement = Entity::write_by_id(req.announcement_id, &auth)? .columns([Column::Id, Column::ContestId]) .one(self.db.deref()) @@ -293,10 +271,7 @@ impl Announcement for ArcServer { } #[instrument(skip_all, level = "debug")] async fn publish(&self, req: Request) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let perm = auth.user_perm(); tracing::debug!(id = req.id); @@ -326,10 +301,7 @@ impl Announcement for ArcServer { } #[instrument(skip_all, level = "debug")] async fn unpublish(&self, req: Request) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let perm = auth.user_perm(); tracing::debug!(id = req.id); @@ -362,10 +334,7 @@ impl Announcement for ArcServer { &self, req: Request, ) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let parent: contest::IdModel = contest::Entity::related_read_by_id(&auth, Into::::into(req.contest_id), &self.db) diff --git a/backend/src/endpoint/contest.rs b/backend/src/endpoint/contest.rs index db11b060..b3f1ab6a 100644 --- a/backend/src/endpoint/contest.rs +++ b/backend/src/endpoint/contest.rs @@ -62,13 +62,7 @@ impl Contest for ArcServer { &self, req: Request, ) -> Result, Status> { - let (auth, req) = self - .parse_request_fn(req, |req| { - (req.size + req.offset.saturating_abs() as u64 / 5 + 2) - .try_into() - .unwrap_or(u32::MAX) - }) - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; req.bound_check()?; @@ -101,10 +95,7 @@ impl Contest for ArcServer { } #[instrument(skip_all, level = "debug")] async fn full_info(&self, req: Request) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let query = Entity::find_by_id::(req.into()).filter(Column::Public.eq(true)); let model = query @@ -118,10 +109,7 @@ impl Contest for ArcServer { } #[instrument(skip_all, level = "debug")] async fn create(&self, req: Request) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let (user_id, perm) = auth.auth_or_guest()?; req.bound_check()?; @@ -165,10 +153,7 @@ impl Contest for ArcServer { } #[instrument(skip_all, level = "debug")] async fn update(&self, req: Request) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let (user_id, perm) = auth.auth_or_guest()?; req.bound_check()?; @@ -224,10 +209,7 @@ impl Contest for ArcServer { } #[instrument(skip_all, level = "debug")] async fn remove(&self, req: Request) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let result = Entity::write_filter(Entity::delete_by_id(Into::::into(req.id)), &auth)? .exec(self.db.deref()) @@ -245,10 +227,7 @@ impl Contest for ArcServer { } #[instrument(skip_all, level = "debug")] async fn join(&self, req: Request) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let (user_id, perm) = auth.auth_or_guest()?; let model = Entity::read_filter(Entity::find_by_id(req.id), &auth)? diff --git a/backend/src/endpoint/education.rs b/backend/src/endpoint/education.rs index 7158358c..595597df 100644 --- a/backend/src/endpoint/education.rs +++ b/backend/src/endpoint/education.rs @@ -37,13 +37,7 @@ impl Education for ArcServer { &self, req: Request, ) -> Result, Status> { - let (auth, req) = self - .parse_request_fn(req, |req| { - (req.size + req.offset.saturating_abs() as u64 / 5 + 2) - .try_into() - .unwrap_or(u32::MAX) - }) - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; req.bound_check()?; @@ -76,10 +70,7 @@ impl Education for ArcServer { } #[instrument(skip_all, level = "debug")] async fn create(&self, req: Request) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let (user_id, perm) = auth.auth_or_guest()?; req.bound_check()?; @@ -113,10 +104,7 @@ impl Education for ArcServer { } #[instrument(skip_all, level = "debug")] async fn update(&self, req: Request) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let (user_id, _perm) = auth.auth_or_guest()?; req.bound_check()?; @@ -150,10 +138,7 @@ impl Education for ArcServer { } #[instrument(skip_all, level = "debug")] async fn remove(&self, req: Request) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let result = Entity::write_filter(Entity::delete_by_id(Into::::into(req.id)), &auth)? .exec(self.db.deref()) @@ -174,10 +159,7 @@ impl Education for ArcServer { &self, req: Request, ) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let (user_id, perm) = auth.auth_or_guest()?; let (problem, model) = tokio::try_join!( @@ -217,10 +199,7 @@ impl Education for ArcServer { &self, req: Request, ) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let mut model = Entity::write_by_id(req.problem_id, &auth)? .columns([Column::Id, Column::ProblemId]) @@ -247,10 +226,7 @@ impl Education for ArcServer { &self, req: Request, ) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let parent: problem::IdModel = problem::Entity::related_read_by_id(&auth, Into::::into(req.problem_id), &self.db) diff --git a/backend/src/endpoint/problem.rs b/backend/src/endpoint/problem.rs index df359fcd..04c8af98 100644 --- a/backend/src/endpoint/problem.rs +++ b/backend/src/endpoint/problem.rs @@ -48,13 +48,7 @@ impl Problem for ArcServer { &self, req: Request, ) -> Result, Status> { - let (auth, req) = self - .parse_request_fn(req, |req| { - (req.size + req.offset.saturating_abs() as u64 / 5 + 2) - .try_into() - .unwrap_or(u32::MAX) - }) - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; req.bound_check()?; @@ -89,10 +83,7 @@ impl Problem for ArcServer { } #[instrument(skip_all, level = "debug")] async fn full_info(&self, req: Request) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; debug!(problem_id = req.id); @@ -108,10 +99,7 @@ impl Problem for ArcServer { } #[instrument(skip_all, level = "debug")] async fn create(&self, req: Request) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let (user_id, perm) = auth.auth_or_guest()?; req.bound_check()?; @@ -152,10 +140,7 @@ impl Problem for ArcServer { } #[instrument(skip_all, level = "debug")] async fn update(&self, req: Request) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let (user_id, _perm) = auth.auth_or_guest()?; req.bound_check()?; @@ -190,10 +175,7 @@ impl Problem for ArcServer { } #[instrument(skip_all, level = "debug")] async fn remove(&self, req: Request) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let result = Entity::write_filter(Entity::delete_by_id(Into::::into(req.id)), &auth)? .exec(self.db.deref()) @@ -214,10 +196,7 @@ impl Problem for ArcServer { &self, req: Request, ) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let (user_id, perm) = auth.auth_or_guest()?; if !perm.admin() { @@ -264,10 +243,7 @@ impl Problem for ArcServer { &self, req: Request, ) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let mut problem = Entity::write_by_id(req.problem_id, &auth)? .columns([Column::Id, Column::ContestId]) @@ -290,10 +266,7 @@ impl Problem for ArcServer { } #[instrument(skip_all, level = "debug")] async fn publish(&self, req: Request) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let (_, perm) = auth.auth_or_guest()?; tracing::debug!(id = req.id); @@ -325,10 +298,7 @@ impl Problem for ArcServer { } #[instrument(skip_all, level = "debug")] async fn unpublish(&self, req: Request) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let (_, perm) = auth.auth_or_guest()?; tracing::debug!(id = req.id); @@ -363,10 +333,7 @@ impl Problem for ArcServer { &self, req: Request, ) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let parent: contest::IdModel = contest::Entity::related_read_by_id(&auth, Into::::into(req.contest_id), &self.db) diff --git a/backend/src/endpoint/submit.rs b/backend/src/endpoint/submit.rs index adaaddbc..69d03a57 100644 --- a/backend/src/endpoint/submit.rs +++ b/backend/src/endpoint/submit.rs @@ -56,13 +56,7 @@ impl Submit for ArcServer { &self, req: Request, ) -> Result, Status> { - let (auth, req) = self - .parse_request_fn(req, |req| { - (req.size + req.offset.saturating_abs() as u64 / 5 + 2) - .try_into() - .unwrap_or(u32::MAX) - }) - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; req.bound_check()?; @@ -92,10 +86,7 @@ impl Submit for ArcServer { } #[instrument(skip_all, level = "debug")] async fn info(&self, req: Request) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; tracing::debug!(id = req.id); @@ -110,7 +101,7 @@ impl Submit for ArcServer { } #[instrument(skip_all, level = "debug")] async fn create(&self, req: Request) -> Result, Status> { - let (auth, req) = self.parse_request_n(req, crate::NonZeroU32!(15)).await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let (user_id, _) = auth.auth_or_guest()?; req.bound_check()?; @@ -165,10 +156,7 @@ impl Submit for ArcServer { #[instrument(skip_all, level = "debug")] async fn remove(&self, req: Request) -> std::result::Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let result = Entity::write_filter(Entity::delete_by_id(req.id), &auth)? .exec(self.db.deref()) @@ -208,10 +196,7 @@ impl Submit for ArcServer { #[instrument(skip_all, level = "debug")] async fn rejudge(&self, req: Request) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let (user_id, perm) = auth.auth_or_guest()?; let submit_id = req.submit_id; diff --git a/backend/src/endpoint/testcase.rs b/backend/src/endpoint/testcase.rs index 3640d17b..25d77b8b 100644 --- a/backend/src/endpoint/testcase.rs +++ b/backend/src/endpoint/testcase.rs @@ -40,10 +40,7 @@ impl Testcase for ArcServer { } #[instrument(skip_all, level = "debug")] async fn create(&self, req: Request) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let (user_id, perm) = auth.auth_or_guest()?; req.bound_check()?; @@ -78,10 +75,7 @@ impl Testcase for ArcServer { } #[instrument(skip_all, level = "debug")] async fn update(&self, req: Request) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let (user_id, _perm) = auth.auth_or_guest()?; req.bound_check()?; @@ -115,10 +109,7 @@ impl Testcase for ArcServer { } #[instrument(skip_all, level = "debug")] async fn remove(&self, req: Request) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let result = Entity::write_filter(Entity::delete_by_id(Into::::into(req.id)), &auth)? .exec(self.db.deref()) @@ -139,10 +130,7 @@ impl Testcase for ArcServer { &self, req: Request, ) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let (user_id, perm) = auth.auth_or_guest()?; if !perm.super_user() { @@ -181,10 +169,7 @@ impl Testcase for ArcServer { &self, req: Request, ) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let mut test = Entity::write_by_id(req.problem_id, &auth)? .columns([Column::Id, Column::ProblemId]) @@ -209,10 +194,7 @@ impl Testcase for ArcServer { &self, req: Request, ) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; tracing::debug!(problem_id = req.problem_id, testcase_id = req.testcase_id); diff --git a/backend/src/endpoint/token.rs b/backend/src/endpoint/token.rs index 21a9ca0c..a836c898 100644 --- a/backend/src/endpoint/token.rs +++ b/backend/src/endpoint/token.rs @@ -22,10 +22,7 @@ impl From for String { impl Token for ArcServer { #[instrument(skip_all, level = "debug")] async fn list(&self, req: Request) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let (user_id, perm) = auth.auth_or_guest()?; if req.id != user_id && !perm.root() { @@ -49,10 +46,7 @@ impl Token for ArcServer { #[instrument(skip_all, level = "debug")] async fn create(&self, req: Request) -> Result, Status> { // FIXME: limit token count - let (_, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (_, req) = self.rate_limit(req).in_current_span().await?; tracing::debug!(username = req.username); diff --git a/backend/src/endpoint/user.rs b/backend/src/endpoint/user.rs index a7f99eac..3c8064c0 100644 --- a/backend/src/endpoint/user.rs +++ b/backend/src/endpoint/user.rs @@ -40,13 +40,7 @@ impl User for ArcServer { &self, req: Request, ) -> Result, Status> { - let (auth, req) = self - .parse_request_fn(req, |req| { - (req.size + req.offset.saturating_abs() as u64 / 5 + 2) - .try_into() - .unwrap_or(u32::MAX) - }) - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; req.bound_check()?; @@ -85,10 +79,7 @@ impl User for ArcServer { } #[instrument(skip_all, level = "debug")] async fn create(&self, req: Request) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; req.bound_check()?; @@ -156,10 +147,7 @@ impl User for ArcServer { } #[instrument(skip_all, level = "debug")] async fn update(&self, req: Request) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let (user_id, perm) = auth.auth_or_guest()?; @@ -215,10 +203,7 @@ impl User for ArcServer { } #[instrument(skip_all, level = "debug")] async fn remove(&self, req: Request) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let result = Entity::write_filter(Entity::delete_by_id(Into::::into(req.id)), &auth)? .exec(self.db.deref()) @@ -239,10 +224,7 @@ impl User for ArcServer { &self, req: Request, ) -> Result, Status> { - let (auth, req) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let (user_id, _) = auth.auth_or_guest()?; let model = user::Entity::find() @@ -273,10 +255,7 @@ impl User for ArcServer { #[instrument(skip_all, level = "debug")] async fn my_info(&self, req: Request<()>) -> Result, Status> { - let (auth, _) = self - .parse_request_n(req, NonZeroU32!(5)) - .in_current_span() - .await?; + let (auth, req) = self.rate_limit(req).in_current_span().await?; let (user_id, _) = auth.auth_or_guest()?; let model = Entity::find_by_id(user_id) diff --git a/backend/src/util/mod.rs b/backend/src/util/mod.rs index caeaf085..9e095fc6 100644 --- a/backend/src/util/mod.rs +++ b/backend/src/util/mod.rs @@ -2,5 +2,5 @@ pub mod auth; pub mod bound; pub mod code; pub mod error; -pub mod parse; +pub mod rate_limit; pub mod time; diff --git a/backend/src/util/parse.rs b/backend/src/util/parse.rs deleted file mode 100644 index 08df1c29..00000000 --- a/backend/src/util/parse.rs +++ /dev/null @@ -1,82 +0,0 @@ -use std::num::NonZeroU32; - -use tracing::*; - -use crate::{ - controller::rate_limit::{Bucket, TrafficType}, - server::Server, -}; - -use super::auth::Auth; - -impl Server { - /// parse authication without rate limiting - /// - /// It's useful for endpoints that require resolving identity before rate limiting - pub async fn parse_auth( - &self, - req: &tonic::Request, - ) -> Result<(Auth, Bucket), tonic::Status> { - let mut auth = Auth::Guest; - - let bucket = self - .rate_limit - .check(req, |req| async { - if let Some(x) = req.metadata().get("token") { - let token = x.to_str().unwrap(); - - match self.token.verify(token).await { - Ok(user) => { - tracing::debug!(user_id = user.0); - auth = Auth::User(user); - TrafficType::Login(user.0) - } - Err(err) => { - tracing::debug!(msg = err.to_string()); - TrafficType::Blacklist(err) - } - } - } else { - tracing::debug!("token_missing"); - TrafficType::Guest - } - }) - .await?; - Ok((auth, bucket)) - } - /// parse request for payload and immediately rate - /// limiting base on a const cost - #[inline] - #[instrument(skip_all, level = "info", name = "parse")] - pub async fn parse_request_n( - &self, - req: tonic::Request, - permit: NonZeroU32, - ) -> Result<(Auth, T), tonic::Status> { - let (auth, bucket) = self.parse_auth(&req).await?; - - bucket.cost(permit)?; - - Ok((auth, req.into_inner())) - } - /// parse request for payload and immediately rate - /// limiting base on a dynamic cost(calculated by a function) - #[inline] - pub async fn parse_request_fn( - &self, - req: tonic::Request, - f: F, - ) -> Result<(Auth, T), tonic::Status> - where - F: FnOnce(&T) -> u32, - { - let (auth, bucket) = self.parse_auth(&req).await?; - let req = req.into_inner(); - - if let Some(cost) = NonZeroU32::new(f(&req)) { - bucket.cost(cost)?; - } - - Ok((auth, req)) - } -} diff --git a/backend/src/util/rate_limit.rs b/backend/src/util/rate_limit.rs new file mode 100644 index 00000000..34795220 --- /dev/null +++ b/backend/src/util/rate_limit.rs @@ -0,0 +1,197 @@ +use std::num::NonZeroU32; + +use crate::{ + controller::rate_limit::{Bucket, TrafficType}, + server::Server, +}; +use grpc::backend::{Id, *}; +use tracing::*; + +use super::auth::Auth; + +impl Server { + /// parse authentication without rate limiting + /// + /// It's useful for endpoints that require resolving identity + /// before rate limiting, such as logout + pub async fn parse_auth( + &self, + req: &tonic::Request, + ) -> Result<(Auth, Bucket), tonic::Status> { + let mut auth = Auth::Guest; + + let bucket = self + .rate_limit + .check(req, |req| async { + if let Some(x) = req.metadata().get("token") { + let token = x.to_str().unwrap(); + + match self.token.verify(token).in_current_span().await { + Ok(user) => { + tracing::debug!(user_id = user.0); + auth = Auth::User(user); + TrafficType::Login(user.0) + } + Err(err) => { + tracing::debug!(msg = err.to_string()); + TrafficType::Blacklist(err) + } + } + } else { + tracing::debug!("token_missing"); + TrafficType::Guest + } + }) + .await?; + Ok((auth, bucket)) + } + /// parse request for payload and immediately rate + /// limiting base on a const cost + #[inline] + #[instrument(skip_all, level = "info", name = "parse")] + pub async fn parse_request_n( + &self, + req: tonic::Request, + permit: NonZeroU32, + ) -> Result<(Auth, T), tonic::Status> { + let (auth, bucket) = self.parse_auth(&req).await?; + + bucket.cost(permit)?; + + Ok((auth, req.into_inner())) + } + /// parse request for payload and immediately rate + /// limiting base on a dynamic cost(calculated by a function) + #[inline] + pub async fn parse_request_fn( + &self, + req: tonic::Request, + f: F, + ) -> Result<(Auth, T), tonic::Status> + where + F: FnOnce(&T) -> u32, + { + let (auth, bucket) = self.parse_auth(&req).await?; + let req = req.into_inner(); + + if let Some(cost) = NonZeroU32::new(f(&req)) { + bucket.cost(cost)?; + } + + Ok((auth, req)) + } + pub async fn rate_limit( + &self, + req: tonic::Request, + ) -> Result<(Auth, T), tonic::Status> { + let (auth, bucket) = self.parse_auth(&req).in_current_span().await?; + bucket.cost(NonZeroU32::new(3).unwrap())?; + let req = req.into_inner(); + + if let Some(cost) = NonZeroU32::new(req.get_cost()) { + bucket.cost(cost)?; + } + + Ok((auth, req)) + } +} + +pub trait RateLimit { + fn get_cost(&self) -> u32 { + 10 + } +} + +macro_rules! impl_list_rate_limit { + ($t:ident) => { + paste::paste!{ + impl RateLimit for []{ + fn get_cost(&self) -> u32 { + self.size.saturating_add(self.offset.unsigned_abs() / 7).saturating_add(5).min(u32::MAX as u64) as u32 + } + } + } + }; +} +impl_list_rate_limit!(Problem); +impl_list_rate_limit!(Contest); +impl_list_rate_limit!(Submit); +impl_list_rate_limit!(Education); +impl_list_rate_limit!(Testcase); +impl_list_rate_limit!(Announcement); + +impl RateLimit for ListUserRequest { + fn get_cost(&self) -> u32 { + self.size + .saturating_add(self.offset.abs() as u64 / 6) + .saturating_add(12) + .min(u32::MAX as u64) as u32 + } +} + +impl RateLimit for ListChatRequest { + fn get_cost(&self) -> u32 { + self.size + .saturating_add(self.offset.abs() as u64 / 8) + .saturating_add(3) + .min(u32::MAX as u64) as u32 + } +} + +impl RateLimit for Id {} + +macro_rules! impl_basic_rate_limit { + ($t:ident) => { + paste::paste! { + impl RateLimit for []{ + fn get_cost(&self) -> u32 { + 17 + } + } + impl RateLimit for []{ + fn get_cost(&self) -> u32 { + 15 + } + } + } + }; +} +impl_basic_rate_limit!(Problem); +impl_basic_rate_limit!(Contest); +impl_basic_rate_limit!(Education); +impl_basic_rate_limit!(Testcase); +impl_basic_rate_limit!(Announcement); +impl_basic_rate_limit!(User); + +impl RateLimit for UpdatePasswordRequest { + fn get_cost(&self) -> u32 { + 230 + } +} +impl RateLimit for CreateSubmitRequest { + fn get_cost(&self) -> u32 { + 430 + } +} +impl RateLimit for CreateChatRequest { + fn get_cost(&self) -> u32 { + 10 + } +} + +impl RateLimit for AddAnnouncementToContestRequest {} + +impl RateLimit for AddEducationToProblemRequest {} + +impl RateLimit for AddTestcaseToProblemRequest {} +impl RateLimit for AddProblemToContestRequest {} +impl RateLimit for JoinContestRequest {} + +impl RateLimit for RejudgeRequest {} + +impl RateLimit for LoginRequest { + fn get_cost(&self) -> u32 { + 200 + } +} +impl RateLimit for () {} diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml index aec1e953..3632aade 100644 --- a/docker/dev/docker-compose.yml +++ b/docker/dev/docker-compose.yml @@ -51,6 +51,8 @@ services: profiles: [backend-dev, frontend-dev] ports: - "16686:16686" + - "4317:4317/tcp" + - "4317:4317/udp" networks: - jaeger migration: diff --git a/justfile b/justfile index 84cccc2f..155a97b6 100644 --- a/justfile +++ b/justfile @@ -1,18 +1,11 @@ -# dev: -# just dev-frontend & P1=$! & just dev-backend & P2=$! && wait P1 P2 - dev-frontend: cd frontend && just dev dev-backend: - cd backend && just run + cd backend && just dev dev-judger: - cd judger && sudo just podman-run - -prepare: - # mkdir -p cert - # openssl req -x509 -newkey rsa:4096 -keyout cert/key.pem -out cert/cert.pem -sha256 -days 3650 -nodes -subj "/C=XX/ST=StateName/L=CityName/O=CompanyName/OU=CompanySectionName/CN=localhost" + cd judger && cargo run clean: cargo clean