netwo-io / apistos

Actix-web wrapper for automatic OpenAPI 3.0 documentation generation.
MIT License
146 stars 5 forks source link

Issue with middleware HttpAuthentication #136

Open AzHicham opened 2 weeks ago

AzHicham commented 2 weeks ago

Hello,

I'm trying to use the middleware HttpAuthentication (from actix-web-httpauth), but I'm facing a compile issue since I'm using the wrap function from apistos and not directly actix.

Is this expected ?

my code :

#[derive(ApiSecurity, Clone)]
#[openapi_security(scheme(security_type(api_key(name = "x-api-key", api_key_in = "header"))))]
pub struct ApiKey(pub String);

impl FromRequest for ApiKey {
... code omitted 
}

pub async fn validator(
    req: ServiceRequest,
    api_key: ApiKey,
) -> Result<ServiceRequest, (actix_web::Error, ServiceRequest)> {
    Ok(req)
}

#[must_use]
pub fn config<T: GetAnalysisResult>() -> Scope
where
    AppError: From<T::Err>,
{
    let auth = HttpAuthentication::with_fn(validator);
    scope("/result")
        .wrap(auth.clone())           /// <------ The issue is here 
        .service(resource("{id}/check").route(get().to(get_result::<T>)))
        .service(resource("{id}/report").route(get().to(get_report::<T>)))
}

 = note: expected struct `apistos::web::Scope<actix_web::Scope<actix_web::scope::ScopeEndpoint>>`
               found struct `apistos::web::Scope<actix_web::Scope<impl ServiceFactory<ServiceRequest, Config = (), Response = ServiceResponse<EitherBody<BoxBody>>, Error = actix_web::Error, InitError = ()>>>`

Thanks you for your help

rlebran commented 2 weeks ago

Hi,

For the config function, do you use it before calling document or after ? Is the Scope coming from actix web or apistos ?

AzHicham commented 3 days ago

Sorry for the late reply Scope is coming from apistos and the config fn is called after document

Here is my setup :

let server = HttpServer::new(move || {
        actix_web::App::new()
            .document(spec())
            .wrap(ErrorHandlers::new().handler(StatusCode::NOT_FOUND, add_error_header))
            .wrap(actix_logger())
            .app_data(json_payload_config())
            .wrap(no_cache_headers())
            .wrap_fn(|mut req, srv| {
                lowercase_path(&mut req);
                srv.call(req)
            })
            .app_data(backend_v0.clone())
            .app_data(backend_v1.clone())
            .configure(|cfg| {
                cfg.service(v0::config::<BackendV0>())
                    .service(v1::config::<BackendV1>())
                    .service(status::config::<BackendV0, BackendV1>());
            })
            .build_with(
                "/openapi.json",
                BuildConfig::default()
                    .with(SwaggerUIConfig::new(&"/swagger"))
                    .with(RedocConfig::new(&"/redoc")),
            )
    })
    .bind((host, port))?
    .workers(nb_workers as usize)
    .run();
AzHicham commented 3 days ago

I make it work by duplicating wrap ... that's really weird. I cannot attach attach the auth with wrap to the scope, only inside fn service()

#[must_use]
fn public<T: Backend + StatusService + AnalysisService + GetAnalysisResult>() -> Scope
where
    AppError: From<<T as AnalysisService>::Err>,
    AppError: From<<T as GetAnalysisResult>::Err>,
{
    let auth = HttpAuthentication::with_fn(validator::<T>);
    scope("/public")
        .service(status::config::<T>())
        .service(analysis::config::<T>().wrap(auth.clone()))
        .service(result::config::<T>().wrap(auth.clone()))
}