diff --git a/services/reports/src/service.rs b/services/reports/src/service.rs --- a/services/reports/src/service.rs +++ b/services/reports/src/service.rs @@ -1,7 +1,7 @@ use actix_web::FromRequest; use chrono::Utc; use comm_services_lib::{ - auth::UserIdentity, + auth::{AuthService, AuthorizationCredential}, blob::client::{BlobServiceClient, BlobServiceError}, crypto::aes256, database, @@ -9,10 +9,11 @@ use derive_more::{Display, Error, From}; use std::{ collections::HashMap, - future::{ready, Ready}, + future::{ready, Future}, + pin::Pin, sync::Arc, }; -use tracing::{error, trace}; +use tracing::{error, trace, warn}; use crate::{ config::CONFIG, @@ -71,13 +72,20 @@ } } - pub fn authenticated(&self, user: UserIdentity) -> Self { - let user_id = user.user_id.to_string(); + /// Clones the service with a new auth identity. When the credential is + /// a service-to-service, the `user_id` is None. + pub fn with_authentication(&self, token: AuthorizationCredential) -> Self { + let requesting_user_id = match &token { + AuthorizationCredential::ServicesToken(_) => None, + AuthorizationCredential::UserToken(user) => { + Some(user.user_id.to_string()) + } + }; Self { db: self.db.clone(), email_config: self.email_config.clone(), - blob_client: self.blob_client.with_user_identity(user), - requesting_user_id: Some(user_id), + blob_client: self.blob_client.with_authentication(token), + requesting_user_id, } } @@ -195,35 +203,62 @@ impl FromRequest for ReportsService { type Error = actix_web::Error; - type Future = Ready>; + type Future = Pin>>>; #[inline] fn from_request( req: &actix_web::HttpRequest, _payload: &mut actix_web::dev::Payload, ) -> Self::Future { + use actix_web::error::{ErrorForbidden, ErrorInternalServerError}; use actix_web::HttpMessage; - let Some(service) = req.app_data::() else { - tracing::error!( - "FATAL! Failed to extract ReportsService from actix app_data. \ - Check HTTP server configuration" - ); - return ready(Err(actix_web::error::ErrorInternalServerError("Internal server error"))); - }; + let base_service = + req.app_data::().cloned().ok_or_else(|| { + tracing::error!( + "FATAL! Failed to extract ReportsService from actix app_data. \ + Check HTTP server configuration" + ); + ErrorInternalServerError("Internal server error") + }); let auth_service = - if let Some(user_identity) = req.extensions().get::() { - tracing::trace!("Found user identity. Creating authenticated service"); - service.authenticated(user_identity.clone()) - } else { - tracing::trace!( - "No user identity found. Leaving unauthenticated service" + req.app_data::().cloned().ok_or_else(|| { + tracing::error!( + "FATAL! Failed to extract AuthService from actix app_data. \ + Check HTTP server configuration" ); - service.clone() - }; + ErrorInternalServerError("Internal server error") + }); - ready(Ok(auth_service)) + let request_auth_value = + req.extensions().get::().cloned(); + + Box::pin(async move { + let auth_service = auth_service?; + let base_service = base_service?; + + // This is Some for endpoints hidden behind auth validation middleware + let auth_token = match request_auth_value { + Some(token @ AuthorizationCredential::UserToken(_)) => token, + Some(_) => { + // Reports service shouldn't be called by other services + warn!("Reports service requires user authorization"); + return Err(ErrorForbidden("Forbidden")); + } + None => { + // Unauthenticated requests get a service-to-service token + let services_token = + auth_service.get_services_token().await.map_err(|err| { + error!("Failed to get services token: {err}"); + ErrorInternalServerError("Internal server error") + })?; + AuthorizationCredential::ServicesToken(services_token) + } + }; + let service = base_service.with_authentication(auth_token); + Ok(service) + }) } }