diff --git a/shared/comm-lib/src/auth/mod.rs b/shared/comm-lib/src/auth/mod.rs --- a/shared/comm-lib/src/auth/mod.rs +++ b/shared/comm-lib/src/auth/mod.rs @@ -5,3 +5,23 @@ #[cfg(feature = "aws")] pub use service::*; pub use types::*; + +use crate::constants::DISABLE_CSAT_VERIFICATION_ENV_VAR; +use once_cell::sync::Lazy; + +static CSAT_VERIFICATION_DISABLED: Lazy = Lazy::new(|| { + let is_disabled = std::env::var(DISABLE_CSAT_VERIFICATION_ENV_VAR) + .is_ok_and(|value| ["1", "true"].contains(&value.as_str())); + + if is_disabled { + tracing::warn!( + "CSAT verification is disabled! All requests will be unauthenticated!" + ); + } + + is_disabled +}); + +pub fn is_csat_verification_disabled() -> bool { + *Lazy::force(&CSAT_VERIFICATION_DISABLED) +} diff --git a/shared/comm-lib/src/constants.rs b/shared/comm-lib/src/constants.rs --- a/shared/comm-lib/src/constants.rs +++ b/shared/comm-lib/src/constants.rs @@ -17,3 +17,8 @@ // grpc stream may contain more than one message pub const GRPC_CHUNK_SIZE_LIMIT: usize = 4 * 1024 * 1024; pub const GRPC_METADATA_SIZE_PER_MESSAGE: usize = 5; + +/// Environment variable, that when set to `true` disables verifying access +/// token in service requests. +pub const DISABLE_CSAT_VERIFICATION_ENV_VAR: &str = + "COMM_SERVICES_DISABLE_CSAT_VERIFICATION"; diff --git a/shared/comm-lib/src/http/auth.rs b/shared/comm-lib/src/http/auth.rs --- a/shared/comm-lib/src/http/auth.rs +++ b/shared/comm-lib/src/http/auth.rs @@ -13,7 +13,9 @@ use std::str::FromStr; use tracing::debug; -use crate::auth::{AuthorizationCredential, UserIdentity}; +use crate::auth::{ + is_csat_verification_disabled, AuthorizationCredential, UserIdentity, +}; impl FromRequest for AuthorizationCredential { type Error = actix_web::Error; @@ -67,16 +69,55 @@ } } -pub async fn validation_function( +/// Counterpart of [`actix_web_httpauth::extractors::bearer::BearerAuth`] that +/// handles parsing Authorization header into [`AuthorizationCredential`]. +/// The value can be `None` when CSAT verification is disabled. +#[derive(Clone, Debug)] +struct CommServicesBearerAuth { + credential: Option, +} + +impl FromRequest for CommServicesBearerAuth { + type Error = actix_web::Error; + type Future = Ready>; + + fn from_request( + req: &actix_web::HttpRequest, + _payload: &mut actix_web::dev::Payload, + ) -> Self::Future { + use futures_util::future::{err, ok}; + if is_csat_verification_disabled() { + return ok(Self { credential: None }); + } + + match AuthorizationCredential::extract(req).into_inner() { + Ok(credential) => ok(Self { + credential: Some(credential), + }), + Err(e) => err(e), + } + } +} + +/// Function used by auth middleware to validate authenticated requests. +async fn middleware_validation_function( req: ServiceRequest, - bearer: BearerAuth, + auth: CommServicesBearerAuth, ) -> Result { - 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)); - } + let Some(credential) = auth.credential else { + return if is_csat_verification_disabled() { + Ok(req) + } else { + // This branch should be normally unreachable. If this happens, + // it means that `MiddlewareCredentialExtractor::from_request()` + // implementation is incorrect. + tracing::error!( + "CSAT verification enabled, but no credential was extracted!" + ); + let mut error = AuthenticationError::new(Bearer::default()); + *error.status_code_mut() = StatusCode::INTERNAL_SERVER_ERROR; + Err((error.into(), req)) + }; }; // TODO: call identity service, for now just allow every request @@ -121,7 +162,8 @@ Response = ServiceResponse>, Error = actix_web::Error, InitError = (), -> + 'static +> + Clone + + 'static where B: MessageBody + 'static, S: Service< @@ -130,5 +172,5 @@ Error = actix_web::Error, > + 'static, { - HttpAuthentication::bearer(validation_function) + HttpAuthentication::with_fn(middleware_validation_function) }