38 lines
1.3 KiB
Rust
38 lines
1.3 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.
|
|
///
|
|
/// 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, and covers every
|
|
/// state-changing endpoint the same way.
|
|
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, "Cross-site form submission rejected").into_response())
|
|
}
|