Murtuza Kabul
Murtuza Kabul

Reputation: 6514

Rust Actix - early return from middleware

I am trying to return an early response from my middleware in case of the user is not authenticated. Here is the code for that

if authenticate_pass {
        let fut = self.service.call(req);
        Box::pin(async move {
            let res = fut.await?;
            Ok(res)
        })
    } else {
        Box::pin(async move {
            Ok(HttpResponse::Ok().json("Unauthorized")).into()
        })
    }

Exact line I'm getting compiler error is

Ok(HttpResponse::Ok().json("Unauthorized")).into()

which I understand as the object I'm trying to return is not what is being expected. However, I'm very much confused what kind of object is expected here.

The error I'm getting is:

core::convert::Into
pub fn into(self) -> T
Converts this type into the (usually inferred) input type.

the trait bound `Result<ServiceResponse<B>, actix_web::Error>: 
std::convert::From<Result<HttpResponse, _>>` is not satisfied
the following implementations were found:
<Result<(), idna::uts46::Errors> as std::convert::From<idna::uts46::Errors>>
<Result<(), ring::error::Unspecified> as std::convert::From<ring::bssl::Result>>
<Result<miniz_oxide::MZStatus, miniz_oxide::MZError> as 
std::convert::From<&miniz_oxide::StreamResult>>
<Result<miniz_oxide::MZStatus, miniz_oxide::MZError> as 
std::convert::From<&miniz_oxide::StreamResult>>
and 2 others
required because of the requirements on the impl of `Into<Result<ServiceResponse<B>, 
actix_web::Error>>` for `Result<HttpResponse, _>`rustcE0277

Can someone explain what exact return value is expected here.

Upvotes: 0

Views: 1317

Answers (3)

kalind
kalind

Reputation: 1

I was trying to do something similar and ended up creating the ServiceResponse struct like this:

web::scope("admin").wrap_fn(|req, service| {
        let session: TypedSession = req.get_session().into();

        return match session.is_admin() {
            Ok(true) => service.call(req),
            Ok(false) => Box::pin(future::ready(Ok(ServiceResponse::new(
                req.request().clone(),
                HttpResponse::Unauthorized().finish(),
            )))),
            Err(err) => Box::pin(future::ready(Ok(ServiceResponse::new(
                req.request().clone(),
                HttpResponse::from_error(ErrorInternalServerError(err)),
            )))),
        };
    })
}

I think cloning the Request is fine because it is just a wrapper around an Rc.

Upvotes: 0

Chris Andrew
Chris Andrew

Reputation: 113

I'd recommend your middleware collect the authentication state(and whatever other pertinent authorization details), and then insert a value into the Extensions list. This allows you to inject the Authentication data into your handler.

E.G. req.extensions_mut().insert::<AuthUserData>(Arc::new(auth_guard));:

middleware:


pub struct SecurityGuardMiddleware<S> {
    service: Rc<S>,
}

impl<S: 'static, B> Service<ServiceRequest> for SecurityGuardMiddleware<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
    S::Future: 'static,
    B: MessageBody + 'static,
{
    type Response = ServiceResponse<BoxBody>;
    type Error = Error;
    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;

    actix_web::dev::forward_ready!(service);

    fn call(&self, req: ServiceRequest) -> Self::Future {
        let svc = self.service.clone();
        let mut is_authenticated = true;
        let auth_data = Some(true);
        Box::pin(async move {
                req.extensions_mut()
                    .insert::<AuthUserData>(Arc::new(auth_data));
            let res: ServiceResponse = svc.call(req).await?.map_into_boxed_body();
            return Ok(res.map_into_boxed_body());
        })
    }
}

Then implement a FromRequest trait as so. Note: In a production example you would probably collect the authentication data, then evaluate it further in the trait function from_request to determine eligibility. If you really wanted to evaluate it in the Middleware, you would immediately and directly alter the body and return a new ServiceResponse. It's also possible to directly manipulate the Extensions in the middleware as well.:


impl FromRequest for PortalAuthenticated {
    type Error = Error;
    type Future = Ready<Result<Self, Self::Error>>;

    fn from_request(
        req: &actix_web::HttpRequest,
        _payload: &mut actix_web::dev::Payload,
    ) -> Self::Future {
        let value = req.extensions().get::<AuthUserData>().cloned();
        let mut has_portal_auth = false;
        let err_msg = "Request not authorized.";
        let result = match value {
            Some(auth_user_data) => Ok(PortalAuthenticated(auth_user_data)),
            None => Err(actix_web::error::ErrorForbidden(err_msg)),
        };
        ready(result)
    }
}

In your handler:


#[post("/{id}")]
pub async fn create<'a>(
    auth: PortalAuthenticated,
) -> Result<Response<'a, MyStructure>, Error> {

}

Upvotes: 0

Adam Hitchen
Adam Hitchen

Reputation: 66

the trait bound `Result<ServiceResponse<B>, actix_web::Error>: 
std::convert::From<Result<HttpResponse, _>>
...
required because of the requirements on the impl of `Into<Result<ServiceResponse<B>, 
actix_web::Error>>` for `Result<HttpResponse, _>`rustcE0277

indicates that there is no implementation of From<Result<HttpResponse, _>> on ServiceResponse, (or Into<Result<ServiceResponse>, _> for Result<HttpResponse, _>>)

In other words the function expects a Result<ServiceResponse, _> to be returned. A quick scan of the docs suggests this is the type expected: https://docs.rs/actix-web/latest/actix_web/dev/struct.ServiceResponse.html

Upvotes: 1

Related Questions