diff options
author | Vika <vika@fireburn.ru> | 2025-01-01 23:17:56 +0300 |
---|---|---|
committer | Vika <vika@fireburn.ru> | 2025-01-01 23:17:56 +0300 |
commit | d10710326da703f69eaa06723dc66e330fd32745 (patch) | |
tree | e4a32c2a7f2e4a61d1f3f6237977de64e47470ae | |
parent | 675141379067858376698d5f75ab163977d33e3a (diff) | |
download | kittybox-d10710326da703f69eaa06723dc66e330fd32745.tar.zst |
axum: 0.7.9 → 0.8.1
Some breaking changes. For better or for worse. The optional extractor breaking change is a double-edged sword, since not all extractors can be used with `Option<T>` now, and you have to use `Result<T, T::Rejection>` even when you want to ignore an error coming from an extractor, such as `Query`. However, this allows catching errors on authorization extractors even in places where authorization is optional. Change-Id: I35f809d3adf27dbef0e7ee93dc1a7af178b7d014
-rw-r--r-- | Cargo.lock | 26 | ||||
-rw-r--r-- | Cargo.toml | 7 | ||||
-rw-r--r-- | src/frontend/mod.rs | 4 | ||||
-rw-r--r-- | src/frontend/onboarding.rs | 3 | ||||
-rw-r--r-- | src/indieauth/mod.rs | 19 | ||||
-rw-r--r-- | src/indieauth/webauthn.rs | 4 | ||||
-rw-r--r-- | src/lib.rs | 37 | ||||
-rw-r--r-- | src/login.rs | 4 | ||||
-rw-r--r-- | src/main.rs | 2 | ||||
-rw-r--r-- | src/media/mod.rs | 5 | ||||
-rw-r--r-- | src/micropub/mod.rs | 12 |
11 files changed, 61 insertions, 62 deletions
diff --git a/Cargo.lock b/Cargo.lock index 432e1ba..bc54f91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -329,14 +329,14 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axum" -version = "0.7.9" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8" dependencies = [ - "async-trait", "axum-core", "axum-macros", "bytes", + "form_urlencoded", "futures-util", "http", "http-body", @@ -365,11 +365,10 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.4.5" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733" dependencies = [ - "async-trait", "bytes", "futures-util", "http", @@ -386,22 +385,20 @@ dependencies = [ [[package]] name = "axum-extra" -version = "0.9.6" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c794b30c904f0a1c2fb7740f7df7f7972dfaa14ef6f57cb6178dc63e5dca2f04" +checksum = "460fc6f625a1f7705c6cf62d0d070794e94668988b1c38111baeec177c715f7b" dependencies = [ "axum", "axum-core", "bytes", "cookie", - "fastrand", "futures-util", "headers", "http", "http-body", "http-body-util", "mime", - "multer", "pin-project-lite", "serde", "tower", @@ -411,9 +408,9 @@ dependencies = [ [[package]] name = "axum-macros" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" +checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" dependencies = [ "proc-macro2", "quote", @@ -1873,7 +1870,6 @@ version = "0.1.0" dependencies = [ "anyhow", "argon2", - "async-trait", "axum", "axum-extra", "bytes", @@ -2157,9 +2153,9 @@ dependencies = [ [[package]] name = "matchit" -version = "0.7.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "md-5" diff --git a/Cargo.toml b/Cargo.toml index 9d4ba86..20d0f89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,8 +59,8 @@ members = [".", "./util", "./templates", "./indieauth", "./templates-neo", "./to default-members = [".", "./util", "./templates", "./indieauth"] [workspace.dependencies] -axum = "0.7.9" -axum-core = "0.4.5" +axum = "0.8.1" +axum-core = "0.5.0" chrono = { version = "0.4.39", features = ["serde"] } clap = "4.5.23" data-encoding = "2.6.0" @@ -124,9 +124,8 @@ wiremock = "0.6.2" [dependencies] anyhow = { version = "1.0.95", optional = true } argon2 = { version = "0.5.3", features = ["std"] } -async-trait = "0.1.83" axum = { workspace = true, features = ["multipart", "json", "form", "macros"] } -axum-extra = { version = "0.9.6", features = ["cookie", "cookie-signed", "typed-header"] } +axum-extra = { version = "0.10.0", features = ["cookie", "cookie-signed", "typed-header"] } bytes = "1.9.0" chrono = { workspace = true } clap = { workspace = true, features = ["derive"], optional = true } diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs index 9abbcf1..8338ac6 100644 --- a/src/frontend/mod.rs +++ b/src/frontend/mod.rs @@ -1,10 +1,10 @@ use crate::database::{Storage, StorageError}; use axum::{ - extract::{Host, Query, State}, + extract::{Query, State}, http::{StatusCode, Uri}, response::IntoResponse, }; -use axum_extra::headers::HeaderMapExt; +use axum_extra::{extract::Host, headers::HeaderMapExt}; use futures_util::FutureExt; use serde::Deserialize; use std::convert::TryInto; diff --git a/src/frontend/onboarding.rs b/src/frontend/onboarding.rs index a8d2ae6..4588157 100644 --- a/src/frontend/onboarding.rs +++ b/src/frontend/onboarding.rs @@ -2,11 +2,12 @@ use std::sync::Arc; use crate::database::{settings, Storage}; use axum::{ - extract::{FromRef, Host, State}, + extract::{FromRef, State}, http::StatusCode, response::{Html, IntoResponse}, Json, }; +use axum_extra::extract::Host; use kittybox_frontend_renderer::{ErrorPage, OnboardingPage, Template}; use serde::Deserialize; use tokio::{task::JoinSet, sync::Mutex}; diff --git a/src/indieauth/mod.rs b/src/indieauth/mod.rs index 0ac3dfd..ab38715 100644 --- a/src/indieauth/mod.rs +++ b/src/indieauth/mod.rs @@ -3,10 +3,10 @@ use microformats::types::Class; use tracing::error; use serde::Deserialize; use axum::{ - extract::{Form, FromRef, Host, Json, Query, State}, http::StatusCode, response::{Html, IntoResponse, Response} + extract::{Form, FromRef, Json, Query, State}, http::StatusCode, response::{Html, IntoResponse, Response} }; #[cfg_attr(not(feature = "webauthn"), allow(unused_imports))] -use axum_extra::extract::cookie::{CookieJar, Cookie}; +use axum_extra::extract::{Host, cookie::{CookieJar, Cookie}}; use axum_extra::{headers::{authorization::Bearer, Authorization, ContentType, HeaderMapExt}, TypedHeader}; use crate::database::Storage; use kittybox_indieauth::{ @@ -65,7 +65,20 @@ impl axum::response::IntoResponse for IndieAuthResourceError { } } -#[async_trait::async_trait] +impl <A: AuthBackend + FromRef<St>, St: Clone + Send + Sync + 'static> axum::extract::OptionalFromRequestParts<St> for User<A> { + type Rejection = <Self as axum::extract::FromRequestParts<St>>::Rejection; + + async fn from_request_parts(req: &mut axum::http::request::Parts, state: &St) -> Result<Option<Self>, Self::Rejection> { + let res = <Self as axum::extract::FromRequestParts<St>>::from_request_parts(req, state).await; + + match res { + Ok(user) => Ok(Some(user)), + Err(IndieAuthResourceError::Unauthorized) => Ok(None), + Err(err) => Err(err), + } + } +} + impl <A: AuthBackend + FromRef<St>, St: Clone + Send + Sync + 'static> axum::extract::FromRequestParts<St> for User<A> { type Rejection = IndieAuthResourceError; diff --git a/src/indieauth/webauthn.rs b/src/indieauth/webauthn.rs index b7d8c71..0757e72 100644 --- a/src/indieauth/webauthn.rs +++ b/src/indieauth/webauthn.rs @@ -1,9 +1,9 @@ use axum::{ - extract::{Json, Host}, + extract::Json, response::{IntoResponse, Response}, http::StatusCode, Extension }; -use axum_extra::extract::cookie::{CookieJar, Cookie}; +use axum_extra::extract::{Host, cookie::{CookieJar, Cookie}}; use axum_extra::{TypedHeader, headers::{authorization::Bearer, Authorization}}; use super::backend::AuthBackend; diff --git a/src/lib.rs b/src/lib.rs index 5fe3b18..e6bc24c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,8 +3,8 @@ use std::sync::Arc; -use axum::extract::{FromRef, FromRequestParts}; -use axum_extra::extract::{cookie::Key, SignedCookieJar}; +use axum::extract::{FromRef, FromRequestParts, OptionalFromRequestParts}; +use axum_extra::extract::{cookie::{Cookie, Key}, SignedCookieJar}; use database::{FileStorage, PostgresStorage, Storage}; use indieauth::backend::{AuthBackend, FileBackend as FileAuthBackend}; use kittybox_util::queue::JobQueue; @@ -64,36 +64,23 @@ impl axum::response::IntoResponse for NoSessionError { } } -#[async_trait::async_trait] -impl<S> FromRequestParts<S> for Session +impl<S> OptionalFromRequestParts<S> for Session where SessionStore: FromRef<S>, Key: FromRef<S>, S: Send + Sync, { - type Rejection = NoSessionError; + type Rejection = std::convert::Infallible; - async fn from_request_parts(parts: &mut axum::http::request::Parts, state: &S) -> Result<Self, Self::Rejection> { + async fn from_request_parts(parts: &mut axum::http::request::Parts, state: &S) -> Result<Option<Self>, Self::Rejection> { let jar = SignedCookieJar::<Key>::from_request_parts(parts, state).await.unwrap(); let session_store = SessionStore::from_ref(state).read_owned().await; - tracing::debug!("Cookie jar: {:#?}", jar); - let cookie = match jar.get("session_id") { - Some(cookie) => { - tracing::debug!("Session ID cookie: {}", cookie); - cookie - }, - None => { return Err(NoSessionError) } - }; - - session_store.get( - &dbg!(cookie.value_trimmed()) - .parse() - .map_err(|err| { - tracing::error!("Error parsing cookie: {}", err); - NoSessionError - })? - ).cloned().ok_or(NoSessionError) + Ok(jar.get("session_id") + .as_ref() + .map(Cookie::value_trimmed) + .and_then(|id| uuid::Uuid::parse_str(id).ok()) + .and_then(|id| session_store.get(&id).cloned())) } } @@ -264,7 +251,7 @@ pub mod companion { axum::Router::new() .route( - "/:filename", + "/{filename}", axum::routing::get(map_to_static) .layer(Extension(resources)) ) @@ -309,7 +296,7 @@ St: Clone + Send + Sync + 'static .route("/.kittybox/health", get(health_check::<S>)) .nest("/.kittybox/login", crate::login::router::<St, S>()) .route( - "/.kittybox/static/:path", + "/.kittybox/static/{path}", axum::routing::get(crate::frontend::statics) ) .route("/.kittybox/coffee", get(teapot_route)) diff --git a/src/login.rs b/src/login.rs index 19bfaf7..eaa787c 100644 --- a/src/login.rs +++ b/src/login.rs @@ -1,8 +1,8 @@ use std::{borrow::Cow, str::FromStr}; use futures_util::FutureExt; -use axum::{extract::{FromRef, Host, Query, State}, http::HeaderValue, response::IntoResponse, Form}; -use axum_extra::{extract::{cookie::{self, Cookie}, SignedCookieJar}, headers::HeaderMapExt, TypedHeader}; +use axum::{extract::{FromRef, Query, State}, http::HeaderValue, response::IntoResponse, Form}; +use axum_extra::{extract::{Host, cookie::{self, Cookie}, SignedCookieJar}, headers::HeaderMapExt, TypedHeader}; use hyper::{header::{CACHE_CONTROL, LOCATION}, StatusCode}; use kittybox_frontend_renderer::{Template, LoginPage, LogoutPage}; use kittybox_indieauth::{AuthorizationResponse, Error, GrantType, PKCEVerifier, Scope, Scopes}; diff --git a/src/main.rs b/src/main.rs index f57b0be..bd3684e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -224,7 +224,7 @@ async fn main() { } }; - let mut servers: Vec<axum::serve::Serve<_, _>> = vec![]; + let mut servers: Vec<axum::serve::Serve<_, _, _>> = vec![]; let build_hyper = |tcp: std::net::TcpListener| { tracing::info!("Listening on {}", tcp.local_addr().unwrap()); diff --git a/src/media/mod.rs b/src/media/mod.rs index 3ed6810..199f05f 100644 --- a/src/media/mod.rs +++ b/src/media/mod.rs @@ -1,7 +1,8 @@ use axum::{ - extract::{multipart::Multipart, FromRef, Host, Path, State}, response::{IntoResponse, Response} + extract::{multipart::Multipart, FromRef, Path, State}, response::{IntoResponse, Response} }; use axum_extra::headers::{ContentLength, HeaderMapExt, HeaderValue, IfNoneMatch}; +use axum_extra::extract::Host; use axum_extra::TypedHeader; use kittybox_util::micropub::{Error as MicropubError, ErrorKind as ErrorType}; use kittybox_indieauth::Scope; @@ -132,5 +133,5 @@ pub fn router<St, A, M>() -> axum::Router<St> where { axum::Router::new() .route("/", axum::routing::post(upload::<M, A>)) - .route("/uploads/*file", axum::routing::get(serve::<M>)) + .route("/uploads/{*file}", axum::routing::get(serve::<M>)) } diff --git a/src/micropub/mod.rs b/src/micropub/mod.rs index 621d4f9..719fbf0 100644 --- a/src/micropub/mod.rs +++ b/src/micropub/mod.rs @@ -6,8 +6,9 @@ use crate::database::{MicropubChannel, Storage, StorageError}; use crate::indieauth::backend::AuthBackend; use crate::indieauth::User; use crate::micropub::util::form_to_mf2_json; -use axum::extract::{FromRef, Host, Query, State}; +use axum::extract::{FromRef, Query, State}; use axum::body::Body as BodyStream; +use axum_extra::extract::Host; use axum_extra::headers::ContentType; use axum::response::{IntoResponse, Response}; use axum_extra::TypedHeader; @@ -603,13 +604,13 @@ pub(crate) async fn post<D: Storage + 'static, A: AuthBackend>( #[tracing::instrument(skip(db))] pub(crate) async fn query<D: Storage, A: AuthBackend>( State(db): State<D>, - query: Option<Query<MicropubQuery>>, + query: Result<Query<MicropubQuery>, <Query<MicropubQuery> as axum::extract::FromRequestParts<()>>::Rejection>, Host(host): Host, user: User<A>, ) -> axum::response::Response { // We handle the invalid query case manually to return a // MicropubError instead of HTTP 422 - let query = if let Some(Query(query)) = query { + let query = if let Ok(Query(query)) = query { query } else { return MicropubError::from_static( @@ -771,7 +772,8 @@ mod tests { use super::FetchedPostContext; use kittybox_indieauth::{Scopes, Scope, TokenData}; - use axum::extract::{Host, State}; + use axum::extract::State; + use axum_extra::extract::Host; #[test] fn test_populate_reply_context() { @@ -927,7 +929,7 @@ mod tests { async fn test_query_foreign_url() { let res = super::query( State(crate::database::MemoryStorage::default()), - Some(axum::extract::Query(super::MicropubQuery::source( + Ok(axum::extract::Query(super::MicropubQuery::source( "https://aaronparecki.com/feeds/main", ))), Host("aaronparecki.com".to_owned()), |