1use axum::{
4 body::Body,
5 extract::{DefaultBodyLimit, State},
6 http::{header, HeaderValue, Request},
7 middleware,
8 response::IntoResponse,
9 routing::{delete, get, post},
10 Json, Router,
11};
12use serde_json::json;
13use std::sync::Arc;
14use tower_governor::{governor::GovernorConfigBuilder, GovernorLayer};
15use tower_http::{cors::CorsLayer, limit::RequestBodyLimitLayer, trace::TraceLayer};
16use utoipa::OpenApi;
17
18use crate::routes::api_doc::ApiDoc;
19use crate::state::AppState;
20
21pub mod admin;
22pub mod api_doc;
23pub mod manage;
24pub mod noscript;
25pub mod register;
26pub mod tus;
27pub mod upload;
28
29async fn add_security_headers(req: Request<Body>, next: middleware::Next) -> impl IntoResponse {
31 let mut response = next.run(req).await;
32 let headers = response.headers_mut();
33
34 headers.insert(
35 header::X_CONTENT_TYPE_OPTIONS,
36 HeaderValue::from_static("nosniff"),
37 );
38 headers.insert(header::X_FRAME_OPTIONS, HeaderValue::from_static("DENY"));
39 headers.insert(
40 header::CONTENT_SECURITY_POLICY,
41 HeaderValue::from_static(
42 "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'",
43 ),
44 );
45
46 response
47}
48
49pub fn build_router(state: Arc<AppState>) -> Router {
66 let upload_governor_conf = Arc::new(
67 GovernorConfigBuilder::default()
68 .period(std::time::Duration::from_secs(60))
69 .burst_size(state.config.rate_limit_per_minute)
70 .finish()
71 .unwrap(),
72 );
73
74 let cors = CorsLayer::permissive();
75
76 let upload_router = Router::new()
77 .route("/", post(upload::upload_handler))
78 .layer(GovernorLayer {
79 config: upload_governor_conf,
80 })
81 .layer(RequestBodyLimitLayer::new(
82 state.config.max_file_size_bytes as usize + 1024 * 1024,
83 ))
84 .layer(DefaultBodyLimit::max(
85 state.config.max_file_size_bytes as usize + 1024 * 1024,
86 ));
87
88 let tus_router = tus::tus_routes().layer(DefaultBodyLimit::max(
89 state.config.max_file_size_bytes as usize + 1024 * 1024,
90 ));
91
92 Router::new()
93 .nest("/upload", upload_router)
94 .merge(tus_router)
95 .route("/file/:id/info", get(manage::file_info_handler))
96 .route("/file/:id/renew", post(manage::renew_file_id_handler))
97 .route("/file/:id", delete(manage::delete_file_handler))
98 .route("/file/:id/delete", post(manage::delete_file_form_handler))
100 .route(
101 "/api/report",
102 get(noscript::report_form_handler).post(noscript::report_submit_handler),
103 )
104 .route("/api/feedback", post(noscript::feedback_submit_handler))
105 .route("/api/health", get(health_handler))
106 .route("/api/config", get(config_handler))
107 .route("/api/owned-files", post(manage::owned_files_handler))
108 .route("/api/openapi.json", get(openapi_json_handler))
109 .route("/api/docs", get(openapi_json_handler))
110 .route("/api/register", post(register::register_handler))
111 .merge(admin::admin_routes())
112 .layer(middleware::from_fn(add_security_headers))
113 .layer(TraceLayer::new_for_http())
114 .layer(cors)
115 .with_state(state)
116}
117
118#[utoipa::path(
119 get,
120 path = "/api/health",
121 responses(
122 (status = 200, description = "Service health status (juicehost is always unknown since we check passively)", body = serde_json::Value),
123 ),
124 tag = "General",
125)]
126pub async fn health_handler() -> Json<serde_json::Value> {
127 Json(json!({
131 "status": "ok",
132 "juiceback": "ok",
133 "juicehost": "unknown",
134 }))
135}
136
137#[utoipa::path(
138 get,
139 path = "/api/config",
140 responses(
141 (status = 200, description = "Public server configuration", body = serde_json::Value),
142 ),
143 tag = "General",
144)]
145pub async fn config_handler(
146 State(state): State<Arc<AppState>>,
147) -> Json<serde_json::Value> {
148 Json(json!({
149 "max_file_size_bytes": state.config.max_file_size_bytes,
150 "max_ttl_hours": state.config.max_ttl_hours,
151 "default_ttl_hours": state.config.default_ttl_hours,
152 "public_base_url": state.config.public_base_url,
153 "cleanup_interval_minutes": state.config.cleanup_interval_minutes,
154 "rate_limit_per_minute": state.config.rate_limit_per_minute,
155 }))
156}
157
158async fn openapi_json_handler() -> (axum::http::header::HeaderMap, String) {
159 let json = serde_json::to_string_pretty(&ApiDoc::openapi()).unwrap_or_default();
160 let mut headers = axum::http::header::HeaderMap::new();
161 headers.insert(
162 axum::http::header::CONTENT_TYPE,
163 axum::http::HeaderValue::from_static("application/json"),
164 );
165 (headers, json)
166}