Files
komp_ac/web/src/services/mod.rs
2026-08-15 12:44:36 +02:00

52 lines
1.8 KiB
Rust

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<T>(
headers: &HeaderMap,
message: T,
) -> Result<Request<T>, 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<Response> {
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()
})
}