use axum::{ http::{HeaderMap, StatusCode}, response::{IntoResponse, Response}, }; use tonic::{Request, metadata::MetadataValue}; #[derive(Debug)] pub(crate) enum AuthenticationError { Missing, Invalid, } pub(crate) fn authenticated_request( headers: &HeaderMap, message: T, ) -> Result, AuthenticationError> { let token = crate::cookie_value(headers, "analytics_token") .ok_or(AuthenticationError::Missing)?; let value = MetadataValue::try_from(format!("Bearer {token}")) .map_err(|_| AuthenticationError::Invalid)?; let mut request = Request::new(message); request.metadata_mut().insert("authorization", value); Ok(request) } /// Refuses a form POST that another site made the browser send. /// /// For an authenticated endpoint the session cookie is `SameSite=Strict`, so a /// cross-site post arrives without it and fails on authentication anyway; this /// turns that into a plain refusal instead of a redirect to the login page. /// `POST /login` needs the check on its own account: it takes no session but /// hands one out, and Strict governs sending a cookie, not setting one. /// /// Call this *first* in a handler, before any backend request. It is a refusal, /// not a filter — work done ahead of it is work a forged post can make the /// server do. pub(crate) fn reject_cross_site(headers: &HeaderMap) -> Option { headers .get("sec-fetch-site") .is_some_and(|value| value == "cross-site") .then(|| { ( StatusCode::FORBIDDEN, crate::tr!( crate::i18n::Locale::from_headers(headers), "services-cross-site-rejected" ), ) .into_response() }) }