You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I do not understand why this test fails to compile. The layer is working fine in an Axum app.
Do you have any hints regarding that?
Cargo.toml
[package]
name = "basic-auth-layer"version = "0.1.0"edition = "2021"# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tower = "0.4.13"tower-service = "0.3.2"hyper = "0.14.25"http = "0.2.9"tokio = { version = "1.27.0", features = ["full"] }
tower-layer = "0.3.2"http-auth-basic = "0.3.3"axum = "0.6.12"serde = "1.0.160"serde_derive = "1.0.160"eyre = "0.6.8"http-body = "0.4.5"
lib.rs
//! Basic auth middleware that checks credentials and requires user to have a certain role.//!//! The data store to check against must implement [UserPasswordRoleStore].use axum::response::Response;use http::{header,Request,StatusCode};use hyper::Body;use std::future::Future;use std::pin::Pin;use std::sync::Arc;use std::task::{Context,Poll};use tower_layer::Layer;use tower_service::Service;typeUserStore = Arc<Vec<String>>;// Dummy;#[derive(Clone)]pubstructRequireAuthAndRole<T>{inner:T,users:UserStore,required_role:String,}impl<T>RequireAuthAndRole<T>{pubfnnew(inner:T,users:UserStore,required_role:String) -> RequireAuthAndRole<T>{RequireAuthAndRole{
inner,
users,
required_role,}}}// We can implement `Service` for `RequireAuthAndRole<T>` if `T` is a `Service`impl<T,ResBody>Service<Request<Body>>forRequireAuthAndRole<T>whereT:Service<Request<Body>,Response = Response<ResBody>>,T::Future:Send + 'static,ResBody: http_body::Body + Default,{// We don't modify the response type, so we use `T`'s response typetypeResponse = T::Response;// We don't modify the error type, so we use `T`'s response typetypeError = T::Error;typeFuture = Pin<Box<dynFuture<Output = Result<Self::Response,Self::Error>> + Send>>;fnpoll_ready(&mutself,cx:&mutContext<'_>) -> Poll<Result<(),Self::Error>>{self.inner.poll_ready(cx).map_err(Into::into)}fncall(&mutself,req:Request<Body>) -> Self::Future{// Extract basic auth// Check against credentials// Return 401 in case of any errorlet _user_id = matchauthorize(&req,&self.users,self.required_role.clone()){Ok(v) => v,Err(_) => returnBox::pin(async{Ok(err_resp())}),};// Todo: Share authenticated user to downstream// Call the inner service and get a future that resolves to the responselet fut = self.inner.call(req);// We have to box the errors so the types matchlet f = asyncmove{ fut.await};Box::pin(f)}}/// Authorizes user/// 1. Extracts credentials from basic auth header/// 2. Checks if password matches/// 3. Checks required permission against user's permissions [Users]fnauthorize(_req:&Request<Body>,_store:&UserStore,_required_role:String,) -> eyre::Result<String>{Ok("dummy_user".to_string())}/// A layer for wrapping services in `RequireAuthAndRole`#[derive(Clone)]pubstructRequireAuthAndRoleLayer{pubusers:UserStore,pubrequired_role:String,}implRequireAuthAndRoleLayer{pubfnnew(users:UserStore,required_role:String) -> Self{RequireAuthAndRoleLayer{
users,
required_role,}}}impl<S>Layer<S>forRequireAuthAndRoleLayer{typeService = RequireAuthAndRole<S>;fnlayer(&self,service:S) -> RequireAuthAndRole<S>{RequireAuthAndRole::new(service,self.users.clone(),self.required_role.clone())}}/// 401 response when user is not authorizedfnerr_resp() -> Response<axum::body::BoxBody>{letmut res = Response::new(axum::body::BoxBody::default());*res.status_mut() = StatusCode::UNAUTHORIZED;
res.headers_mut().insert(header::WWW_AUTHENTICATE,"Basic".parse().unwrap());
res
}#[cfg(test)]mod tests {#[allow(unused_imports)]usesuper::*;use http::header;use hyper::Body;use tower::{BoxError,ServiceBuilder,ServiceExt};use tower_service::Service;#[allow(unused_imports)]usesuper::*;#[tokio::test]asyncfnaccess_denied_no_role(){letmut user_store = UserStore::default();letmut service = ServiceBuilder::new().layer(RequireAuthAndRoleLayer{required_role:"default".into(),users: user_store,}).service_fn(echo);let request = Request::get("/").header(header::AUTHORIZATION,"bearer foobar").body(Body::empty()).unwrap();let res = service.ready().await.unwrap().call(request).await.unwrap();assert_eq!(res.status(), StatusCode::UNAUTHORIZED);}asyncfnecho(req:Request<Body>) -> Result<Response<Body>,BoxError>{Ok(Response::new(req.into_body()))}}
Thanks
Edit: Current error:
37 | impl<T,ResBody> Service<Request<Body>> forRequireAuthAndRole<T>
| ------- this type parameter
...
59 | Err(_) => returnBox::pin(async {Ok(err_resp())}),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter `ResBody`, found struct `UnsyncBoxBody`
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
Hi!
I have implemented a middleware and tried to get a similar test running as declared in https://github.com/tower-rs/tower-http/blob/master/tower-http/src/auth/require_authorization.rs#L383 .
I do not understand why this test fails to compile. The layer is working fine in an Axum app.
Do you have any hints regarding that?
Cargo.toml
lib.rs
Thanks
Edit: Current error:
Beta Was this translation helpful? Give feedback.
All reactions