diff --git a/services/comm-services-lib/src/http/auth.rs b/services/comm-services-lib/src/http/auth.rs index 7cb2b496e..7776a92fe 100644 --- a/services/comm-services-lib/src/http/auth.rs +++ b/services/comm-services-lib/src/http/auth.rs @@ -1,108 +1,140 @@ use actix_web::{ body::{EitherBody, MessageBody}, dev::{Service, ServiceRequest, ServiceResponse, Transform}, FromRequest, HttpMessage, }; use actix_web_httpauth::{ extractors::{bearer::BearerAuth, AuthenticationError}, headers::www_authenticate::bearer::Bearer, middleware::HttpAuthentication, }; +use http::StatusCode; use std::{ - future::{ready, Ready}, + boxed::Box, + future::{ready, Future, Ready}, + pin::Pin, str::FromStr, }; use tracing::debug; -use crate::auth::UserIdentity; +use crate::auth::{AuthorizationCredential, UserIdentity}; -impl FromRequest for UserIdentity { +impl FromRequest for AuthorizationCredential { type Error = actix_web::Error; type Future = Ready>; fn from_request( req: &actix_web::HttpRequest, _: &mut actix_web::dev::Payload, ) -> Self::Future { - if let Some(user) = req.extensions().get::() { - return ready(Ok(user.clone())); + if let Some(credential) = req.extensions().get::() + { + return ready(Ok(credential.clone())); } let f = || { let bearer = BearerAuth::extract(req).into_inner()?; - let user = match UserIdentity::from_str(bearer.token()) { - Ok(user) => user, + let credential = match AuthorizationCredential::from_str(bearer.token()) { + Ok(credential) => credential, Err(err) => { debug!("HTTP authorization error: {err}"); return Err(AuthenticationError::new(Bearer::default()).into()); } }; - Ok(user) + Ok(credential) }; ready(f()) } } +impl FromRequest for UserIdentity { + type Error = actix_web::Error; + type Future = Pin>>>; + + fn from_request( + req: &actix_web::HttpRequest, + payload: &mut actix_web::dev::Payload, + ) -> Self::Future { + // NOTE: If ever `Ready.into_inner()` gets stable, we can use it here + // to get rid of the async block. Feature name: "ready_into_inner". + // Tracking issue: https://github.com/rust-lang/rust/issues/101196 + let credential_fut = AuthorizationCredential::from_request(req, payload); + let fut = async move { + match credential_fut.await { + Ok(AuthorizationCredential::UserToken(user)) => Ok(user.clone()), + Ok(_) => { + debug!("Authorization provided, but it's not UserIdentity"); + let mut error = AuthenticationError::new(Bearer::default()); + *error.status_code_mut() = StatusCode::FORBIDDEN; + Err(error.into()) + } + Err(err) => Err(err), + } + }; + Box::pin(fut) + } +} + pub async fn validation_function( req: ServiceRequest, bearer: BearerAuth, ) -> Result { - let user = match UserIdentity::from_str(bearer.token()) { - Ok(user) => user, + let credential = match AuthorizationCredential::from_str(bearer.token()) { + Ok(credential) => credential, Err(err) => { debug!("HTTP authorization error: {err}"); return Err((AuthenticationError::new(Bearer::default()).into(), req)); } }; // TODO: call identity service, for now just allow every request - req.extensions_mut().insert(user); + req.extensions_mut().insert(credential); Ok(req) } /// Use this to add Authentication Middleware. It's going to parse Authorization /// header and call the identity service to check if the provided credentials /// are correct. If not it's going to reject the request. /// /// # Example /// ```ignore /// let auth_middleware = get_comm_authentication_middleware(); /// App::new().wrap(auth_middleware); /// ``` /// If you don't want all of the routes to require authentication you can wrap /// individual resources or scopes: /// ```ignore /// App::new().service( /// web::resource("/endpoint").route(web::get().to(handler)).wrap(auth_middleware), /// ) /// ``` // This type is very complicated, but unfortunately typing this directly // requires https://github.com/rust-lang/rust/issues/99697 to be merged. // The issue is that we can't specify the second generic argument of // HttpAuthentication, because it look something like this: // ``` // impl Fn(ServiceRequest, BearerAuth) -> impl Future< // Output = Result, // > // `` // which isn't valid (until the linked issue is merged). pub fn get_comm_authentication_middleware() -> impl Transform< S, ServiceRequest, Response = ServiceResponse>, Error = actix_web::Error, InitError = (), > + 'static where B: MessageBody + 'static, S: Service< ServiceRequest, Response = ServiceResponse, Error = actix_web::Error, > + 'static, { HttpAuthentication::bearer(validation_function) }