aboutsummaryrefslogtreecommitdiffstats
path: root/src/api/mod.rs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-10-31 03:57:45 +0100
committertv <tv@krebsco.de>2026-10-31 03:57:45 +0100
commit98c1a9eefdc1e7d5b5359a437248418c4a6c5d3f (patch)
treedd220cfe9ee8817419167032f86238968f410664 /src/api/mod.rs
parent0f010a383ae256d7e794c85e4332ee45841625aa (diff)
move store helpers to store module
Diffstat (limited to 'src/api/mod.rs')
-rw-r--r--src/api/mod.rs97
1 files changed, 4 insertions, 93 deletions
diff --git a/src/api/mod.rs b/src/api/mod.rs
index 58967f3..a777724 100644
--- a/src/api/mod.rs
+++ b/src/api/mod.rs
@@ -1,4 +1,5 @@
-//! HTTP API: router, the API description and shared response helpers.
+//! HTTP API: router, the API description and shared response helpers (the store's
+//! own are in `store`).
pub(crate) mod auth;
#[cfg(feature = "dashboard")]
@@ -17,19 +18,17 @@ use axum::{
extract::{Request, State},
http::{
HeaderValue, StatusCode,
- header::{CONTENT_LENGTH, CONTENT_TYPE, ETAG},
+ header::{CONTENT_LENGTH, CONTENT_TYPE},
},
middleware::{self, Next},
response::{IntoResponse, Response},
routing::{delete, get},
};
use serde_json::{Value as JsonValue, json};
-use sha2::{Digest, Sha256};
use tracing::{error, warn};
use utoipa_swagger_ui::SwaggerUi;
-use crate::api::auth::{AuthState, AuthenticatedUser, basic_auth};
-use crate::document::meta;
+use crate::api::auth::{AuthState, basic_auth};
use crate::document::store::StoreError;
use crate::state::AppState;
@@ -196,91 +195,3 @@ pub(crate) fn store_status(error: StoreError, context: &str) -> StatusCode {
}
}
}
-
-fn etag_for(bytes: &[u8]) -> String {
- let digest = Sha256::digest(bytes);
- let hex = digest
- .iter()
- .map(|byte| format!("{byte:02x}"))
- .collect::<String>();
-
- format!("\"{hex}\"")
-}
-
-fn parse_key_path(key: &str) -> Result<Vec<String>, StatusCode> {
- let segments: Vec<String> = key.split('/').map(str::to_owned).collect();
-
- if segments.is_empty() || segments.iter().any(|segment| segment.is_empty()) {
- return Err(StatusCode::BAD_REQUEST);
- }
-
- Ok(segments)
-}
-
-/// A path for a write. Protocol data (`_continuum`: remake records, members, forget
-/// records, epoch metadata) is what every node acts on, so a write there acts on the
-/// whole swarm: only users listed in `--protocol-writers` may, and each such write is
-/// logged. Epoch metadata (and `_continuum` as a whole, which contains it) is never
-/// writable: the protocol reads it at the snapshot change, so an edit only misleads
-/// whoever reads it later. `403` otherwise.
-fn parse_write_path(
- state: &AppState,
- user: &AuthenticatedUser,
- key: &str,
- method: &str,
-) -> Result<Vec<String>, StatusCode> {
- let path = parse_key_path(key)?;
- if !meta::is_protocol(&path) {
- return Ok(path);
- }
- // `_continuum` itself, epoch metadata and the swarm ID: nobody.
- let fixed = path.len() < 2 || path[1] == meta::EPOCHS || path[1] == meta::SWARM;
- if fixed || !state.protocol_writers.contains(&user.0) {
- warn!(user = %user.0, method, path = %key, "Refused a write to protocol data");
- return Err(StatusCode::FORBIDDEN);
- }
- warn!(user = %user.0, method, path = %key, "Protocol data written through the store API; this acts on the whole swarm");
- Ok(path)
-}
-
-fn response_for_value(
- value: &JsonValue,
- accept: Option<&HeaderValue>,
-) -> Result<Response, StatusCode> {
- let wants_text = accept
- .and_then(|value| value.to_str().ok())
- .is_some_and(|value| {
- value
- .split(',')
- .any(|item| item.trim().starts_with("text/plain"))
- });
-
- let (content_type, body) = if wants_text {
- let body = match value {
- JsonValue::String(value) => value.clone(),
- other => serde_json::to_string(other).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?,
- };
-
- ("text/plain; charset=utf-8", body)
- } else {
- (
- "application/json",
- serde_json::to_string(value).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?,
- )
- };
-
- let etag = etag_for(body.as_bytes());
-
- let mut response = body.into_response();
-
- response
- .headers_mut()
- .insert(CONTENT_TYPE, HeaderValue::from_static(content_type));
-
- response.headers_mut().insert(
- ETAG,
- HeaderValue::from_str(&etag).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?,
- );
-
- Ok(response)
-}