Compare commits
1 Commits
98775d88b3
...
9e9a63a700
Author | SHA1 | Date |
---|---|---|
|
9e9a63a700 |
|
@ -1362,17 +1362,6 @@ dependencies = [
|
||||||
"hashbrown 0.15.4",
|
"hashbrown 0.15.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "io-uring"
|
|
||||||
version = "0.7.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"cfg-if",
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipnet"
|
name = "ipnet"
|
||||||
version = "2.11.0"
|
version = "2.11.0"
|
||||||
|
@ -1564,7 +1553,7 @@ checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mensa-upb-api"
|
name = "mensa-upb-api"
|
||||||
version = "0.3.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-cors",
|
"actix-cors",
|
||||||
"actix-governor",
|
"actix-governor",
|
||||||
|
@ -2877,19 +2866,17 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.46.0"
|
version = "1.45.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1140bb80481756a8cbe10541f37433b459c5aa1e727b4c020fbfebdc25bf3ec4"
|
checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bytes",
|
"bytes",
|
||||||
"io-uring",
|
|
||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
"slab",
|
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
|
|
|
@ -20,6 +20,6 @@ dotenvy = "0.15.7"
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
sqlx = "0.8.2"
|
sqlx = "0.8.2"
|
||||||
strum = "0.27.1"
|
strum = "0.27.1"
|
||||||
tokio = "1.46.0"
|
tokio = "1.41.1"
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
tracing-subscriber = "0.3.18"
|
tracing-subscriber = "0.3.18"
|
|
@ -5,7 +5,7 @@ license.workspace = true
|
||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
readme.workspace = true
|
readme.workspace = true
|
||||||
version = "0.3.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ impl FromStr for Canteen {
|
||||||
"zm2" => Ok(Self::ZM2),
|
"zm2" => Ok(Self::ZM2),
|
||||||
"basilica" => Ok(Self::Basilica),
|
"basilica" => Ok(Self::Basilica),
|
||||||
"atrium" => Ok(Self::Atrium),
|
"atrium" => Ok(Self::Atrium),
|
||||||
invalid => Err(format!("Invalid canteen identifier: {invalid}")),
|
invalid => Err(format!("Invalid canteen identifier: {}", invalid)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,92 +0,0 @@
|
||||||
use std::{net::IpAddr, str::FromStr};
|
|
||||||
|
|
||||||
use actix_governor::{
|
|
||||||
governor::{
|
|
||||||
clock::{Clock, DefaultClock, QuantaInstant},
|
|
||||||
middleware::NoOpMiddleware,
|
|
||||||
},
|
|
||||||
GovernorConfig, GovernorConfigBuilder, KeyExtractor, SimpleKeyExtractionError,
|
|
||||||
};
|
|
||||||
use actix_web::{
|
|
||||||
dev::ServiceRequest,
|
|
||||||
http::{header::ContentType, StatusCode},
|
|
||||||
HttpResponse, HttpResponseBuilder,
|
|
||||||
};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::USE_X_FORWARDED_HOST;
|
|
||||||
|
|
||||||
pub fn get_governor(
|
|
||||||
seconds_replenish: u64,
|
|
||||||
burst_size: u32,
|
|
||||||
) -> GovernorConfig<UserToken, NoOpMiddleware<QuantaInstant>> {
|
|
||||||
GovernorConfigBuilder::default()
|
|
||||||
.seconds_per_request(seconds_replenish)
|
|
||||||
.burst_size(burst_size)
|
|
||||||
.key_extractor(UserToken)
|
|
||||||
.finish()
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
|
|
||||||
pub struct UserToken;
|
|
||||||
|
|
||||||
impl KeyExtractor for UserToken {
|
|
||||||
type Key = IpAddr;
|
|
||||||
type KeyExtractionError = SimpleKeyExtractionError<&'static str>;
|
|
||||||
|
|
||||||
fn name(&self) -> &'static str {
|
|
||||||
"Bearer token"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract(&self, req: &ServiceRequest) -> Result<Self::Key, Self::KeyExtractionError> {
|
|
||||||
let mut ip = USE_X_FORWARDED_HOST
|
|
||||||
.then(|| {
|
|
||||||
req.headers()
|
|
||||||
.get("X-Forwarded-Host")
|
|
||||||
.and_then(|header| IpAddr::from_str(header.to_str().unwrap_or_default()).ok())
|
|
||||||
})
|
|
||||||
.flatten()
|
|
||||||
.or_else(|| req.peer_addr().map(|socket| socket.ip()))
|
|
||||||
.ok_or_else(|| {
|
|
||||||
Self::KeyExtractionError::new(
|
|
||||||
r#"{ "code": 500, "msg": "Could not extract peer IP address from request"}"#,
|
|
||||||
)
|
|
||||||
.set_content_type(ContentType::json())
|
|
||||||
.set_status_code(StatusCode::INTERNAL_SERVER_ERROR)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// customers often get their own /56 prefix, apply rate-limiting per prefix instead of per
|
|
||||||
// address for IPv6
|
|
||||||
if let IpAddr::V6(ipv6) = ip {
|
|
||||||
let mut octets = ipv6.octets();
|
|
||||||
octets[7..16].fill(0);
|
|
||||||
ip = IpAddr::V6(octets.into());
|
|
||||||
}
|
|
||||||
Ok(ip)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn exceed_rate_limit_response(
|
|
||||||
&self,
|
|
||||||
negative: &actix_governor::governor::NotUntil<QuantaInstant>,
|
|
||||||
mut response: HttpResponseBuilder,
|
|
||||||
) -> HttpResponse {
|
|
||||||
let wait_time = negative
|
|
||||||
.wait_time_from(DefaultClock::default().now())
|
|
||||||
.as_secs();
|
|
||||||
response.content_type(ContentType::json())
|
|
||||||
.body(
|
|
||||||
format!(
|
|
||||||
r#"{{"code":429, "error": "TooManyRequests", "message": "Too many requests, try again after {wait_time} seconds", "after": {wait_time}}}"#
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn whitelisted_keys(&self) -> Vec<Self::Key> {
|
|
||||||
Vec::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn key_name(&self, _key: &Self::Key) -> Option<String> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +1,14 @@
|
||||||
mod canteen;
|
mod canteen;
|
||||||
mod dish;
|
mod dish;
|
||||||
pub mod endpoints;
|
pub mod endpoints;
|
||||||
mod governor;
|
|
||||||
mod menu;
|
mod menu;
|
||||||
|
|
||||||
use std::{error::Error, fmt::Display, sync::LazyLock};
|
use std::{error::Error, fmt::Display};
|
||||||
|
|
||||||
pub use canteen::Canteen;
|
pub use canteen::Canteen;
|
||||||
pub use dish::{Dish, DishPrices};
|
pub use dish::{Dish, DishPrices};
|
||||||
pub use governor::get_governor;
|
|
||||||
pub use menu::Menu;
|
pub use menu::Menu;
|
||||||
|
|
||||||
pub(crate) static USE_X_FORWARDED_HOST: LazyLock<bool> = LazyLock::new(|| {
|
|
||||||
std::env::var("API_USE_X_FORWARDED_HOST")
|
|
||||||
.map(|val| val == "true")
|
|
||||||
.unwrap_or(false)
|
|
||||||
});
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct CustomError(String);
|
struct CustomError(String);
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
use actix_cors::Cors;
|
use actix_cors::Cors;
|
||||||
use actix_governor::Governor;
|
use actix_governor::{Governor, GovernorConfigBuilder};
|
||||||
use actix_web::{web, App, HttpServer};
|
use actix_web::{web, App, HttpServer};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use mensa_upb_api::get_governor;
|
|
||||||
use sqlx::postgres::PgPoolOptions;
|
use sqlx::postgres::PgPoolOptions;
|
||||||
use tracing::{debug, error, info, level_filters::LevelFilter};
|
use tracing::{debug, error, info, level_filters::LevelFilter};
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
@ -42,7 +41,7 @@ async fn main() -> Result<()> {
|
||||||
let burst_size = env::var("API_RATE_LIMIT_BURST")
|
let burst_size = env::var("API_RATE_LIMIT_BURST")
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|s| s.parse::<u32>().ok())
|
.and_then(|s| s.parse::<u32>().ok())
|
||||||
.unwrap_or(20);
|
.unwrap_or(5);
|
||||||
|
|
||||||
let allowed_cors = env::var("API_CORS_ALLOWED")
|
let allowed_cors = env::var("API_CORS_ALLOWED")
|
||||||
.map(|val| {
|
.map(|val| {
|
||||||
|
@ -53,7 +52,11 @@ async fn main() -> Result<()> {
|
||||||
.ok()
|
.ok()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let governor_conf = get_governor(seconds_replenish, burst_size);
|
let governor_conf = GovernorConfigBuilder::default()
|
||||||
|
.seconds_per_request(seconds_replenish)
|
||||||
|
.burst_size(burst_size)
|
||||||
|
.finish()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
info!("Starting server on {}:{}", interface, port);
|
info!("Starting server on {}:{}", interface, port);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue