diff --git a/Cargo.lock b/Cargo.lock index 2f93e62..9a47937 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -112,6 +112,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + [[package]] name = "bytes" version = "1.3.0" @@ -150,6 +156,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +dependencies = [ + "cfg-if", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -160,6 +175,19 @@ dependencies = [ "typenum", ] +[[package]] +name = "dashmap" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" +dependencies = [ + "cfg-if", + "hashbrown", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "digest" version = "0.10.6" @@ -208,25 +236,58 @@ dependencies = [ ] [[package]] -name = "futures-channel" -version = "0.3.25" +name = "futures" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" dependencies = [ "futures-core", + "futures-sink", ] [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" + +[[package]] +name = "futures-executor" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" [[package]] name = "futures-macro" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" dependencies = [ "proc-macro2", "quote", @@ -235,25 +296,35 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" + +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" dependencies = [ + "futures-channel", "futures-core", + "futures-io", "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -277,7 +348,25 @@ checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "governor" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c390a940a5d157878dd057c78680a33ce3415bcd05b4799509ea44210914b4d5" +dependencies = [ + "cfg-if", + "dashmap", + "futures", + "futures-timer", + "no-std-compat", + "nonzero_ext", + "parking_lot", + "quanta", + "rand", + "smallvec", ] [[package]] @@ -401,6 +490,15 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -443,6 +541,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + [[package]] name = "matchit" version = "0.7.0" @@ -479,16 +586,28 @@ checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys", ] +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" + [[package]] name = "nonempty" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" +[[package]] +name = "nonzero_ext" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -603,6 +722,22 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quanta" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20afe714292d5e879d8b12740aa223c6a88f118af41870e8b6196e39a02238a8" +dependencies = [ + "crossbeam-utils", + "libc", + "mach", + "once_cell", + "raw-cpuid", + "wasi 0.10.2+wasi-snapshot-preview1", + "web-sys", + "winapi", +] + [[package]] name = "quote" version = "1.0.23" @@ -642,6 +777,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "raw-cpuid" +version = "10.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6823ea29436221176fe662da99998ad3b4db2c7f31e7b6f5fe43adccd6320bb" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -976,6 +1120,26 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +[[package]] +name = "tower_governor" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6be418f6d18863291f0a7fa1da1de71495a19a54b5fb44969136f731a47e86" +dependencies = [ + "axum", + "forwarded-header-value", + "futures", + "futures-core", + "governor", + "http", + "pin-project", + "thiserror", + "tokio", + "tower", + "tower-layer", + "tracing", +] + [[package]] name = "tracing" version = "0.1.37" @@ -1090,12 +1254,82 @@ dependencies = [ "try-lock", ] +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "web-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1192,6 +1426,7 @@ dependencies = [ "tower", "tower-cookies", "tower-http", + "tower_governor", "tracing", "tracing-subscriber", ] diff --git a/Cargo.toml b/Cargo.toml index 9181b8e..9fdbd0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,16 +6,17 @@ edition = "2021" [dependencies] tokio = { version = "1.23.0", features = ["full"] } axum = { version = "0.6.4", features = ["headers"] } +axum-client-ip = "0.3.1" tower-http = { version = "0.3.5", features = ["fs"] } +tower_governor = "0.0.4" tower-cookies = "0.8.0" tower = "0.4.13" +tracing = "0.1.37" +tracing-subscriber = "0.3.16" bytes = "1.3.0" -axum-client-ip = "0.3.1" serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["std"] } rusqlite = { version = "0.28.0", features = ["bundled"] } rand = "0.8.5" time = "0.3.17" -lazy_static = "1.4.0" -tracing = "0.1.37" -tracing-subscriber = "0.3.16" \ No newline at end of file +lazy_static = "1.4.0" \ No newline at end of file diff --git a/public/js/header.js b/public/js/header.js index 0e251ed..f296567 100644 --- a/public/js/header.js +++ b/public/js/header.js @@ -15,7 +15,7 @@ function header(home, people, user_id) { - ${pfp(user_id)} + ${user_id === undefined ? '' : pfp(user_id)}
diff --git a/public/js/home.js b/public/js/home.js index 0083297..2e5818a 100644 --- a/public/js/home.js +++ b/public/js/home.js @@ -243,7 +243,12 @@ async function load() { } async function init() { - data.user = (await loadself()).json + let request = (await loadself()); + if (request.status === 429) { + header(true, false) + throw new Error("Rate limited"); + } + data.user = request.json header(true, false, data.user.user_id) data.users[data.user.user_id] = data.user const posts = await load() diff --git a/public/js/people.js b/public/js/people.js index b861818..6568ccc 100644 --- a/public/js/people.js +++ b/public/js/people.js @@ -48,7 +48,12 @@ async function loadMore() { } async function load() { - const self = (await loadself()).json + let request = await loadself() + if (request.status === 429) { + header(false, true) + throw new Error("Rate limited"); + } + const self = request.json header(false, true, self.user_id) const users = (await loaduserspage(page)).json if (users.length === 0) { diff --git a/public/js/profile.js b/public/js/profile.js index a043fde..10eb873 100644 --- a/public/js/profile.js +++ b/public/js/profile.js @@ -89,7 +89,14 @@ async function load() { params[key] = value } - data.self = (await loadself()).json; + let request = await loadself() + + if (request.status === 429) { + header(false, false) + throw new Error("Rate limited"); + } + + data.self = request.json; data.users[data.self.user_id] = data.self let id; diff --git a/src/api/mod.rs b/src/api/mod.rs index c347207..d36b127 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,5 +1,48 @@ +use crate::types::extract::RouterURI; +use axum::{Extension, Router, BoxError, error_handling::HandleErrorLayer}; +use tower::ServiceBuilder; +use tower_governor::{governor::GovernorConfigBuilder, GovernorLayer, errors::display_error, key_extractor::SmartIpKeyExtractor}; + pub mod admin; pub mod auth; pub mod pages; pub mod posts; pub mod users; + +pub fn router() -> Router { + let governor_conf = Box::new( + GovernorConfigBuilder::default() + .burst_size(10) + .per_second(1) + .key_extractor(SmartIpKeyExtractor) + .finish() + .expect("Failed to create rate limiter"), + ); + + Router::new() + .nest( + "/admin", + admin::router().layer(Extension(RouterURI("/api/admin"))), + ) + .nest( + "/auth", + auth::router().layer(Extension(RouterURI("/api/auth"))), + ) + .nest( + "/users", + users::router().layer(Extension(RouterURI("/api/users"))), + ) + .nest( + "/posts", + posts::router().layer(Extension(RouterURI("/api/posts"))), + ) + .layer( + ServiceBuilder::new() + .layer(HandleErrorLayer::new(|e: BoxError| async move { + display_error(e) + })) + .layer(GovernorLayer { + config: Box::leak(governor_conf), + }), + ) +} diff --git a/src/main.rs b/src/main.rs index 03cee25..a72ec5f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ use axum::{ http::{Request, StatusCode}, middleware::{self, Next}, response::Response, - Extension, RequestExt, Router, + RequestExt, Router, }; use axum_client_ip::ClientIp; use std::{net::SocketAddr, process::exit}; @@ -14,10 +14,7 @@ use tracing_subscriber::{ }; use types::http::ResponseCode; -use crate::{ - api::{auth, pages, posts, users}, - types::extract::RouterURI, -}; +use crate::api::pages; mod admin; mod api; @@ -77,22 +74,7 @@ async fn main() { .layer(middleware::from_fn(log)) .layer(middleware::from_fn(serve)) .nest("/", pages::router()) - .nest( - "/api/admin", - api::admin::router().layer(Extension(RouterURI("/api/admin"))), - ) - .nest( - "/api/auth", - auth::router().layer(Extension(RouterURI("/api/auth"))), - ) - .nest( - "/api/users", - users::router().layer(Extension(RouterURI("/api/users"))), - ) - .nest( - "/api/posts", - posts::router().layer(Extension(RouterURI("/api/posts"))), - ) + .nest("/api", api::router()) .layer(CookieManagerLayer::new()); let Ok(addr) = "[::]:8080".parse::() else {