treewide: fix various UI bugs; optimize crypto dependencies & format
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: If8fe8b38c1d9c4fecd40ff71f88d2ae06a6a6964
This commit is contained in:
parent
764aafa88d
commit
3ccddce7fd
178 changed files with 58342 additions and 54241 deletions
|
|
@ -1,74 +1,76 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use axum::Router;
|
||||
use axum::extract::DefaultBodyLimit;
|
||||
use axum::http::{HeaderValue, Method, header};
|
||||
use axum::middleware;
|
||||
use axum::routing::{delete, get, patch, post, put};
|
||||
use axum::{
|
||||
Router,
|
||||
extract::DefaultBodyLimit,
|
||||
http::{HeaderValue, Method, header},
|
||||
middleware,
|
||||
routing::{delete, get, patch, post, put},
|
||||
};
|
||||
use tower::ServiceBuilder;
|
||||
use tower_governor::GovernorLayer;
|
||||
use tower_governor::governor::GovernorConfigBuilder;
|
||||
use tower_http::cors::CorsLayer;
|
||||
use tower_http::set_header::SetResponseHeaderLayer;
|
||||
use tower_http::trace::TraceLayer;
|
||||
use tower_governor::{GovernorLayer, governor::GovernorConfigBuilder};
|
||||
use tower_http::{
|
||||
cors::CorsLayer,
|
||||
set_header::SetResponseHeaderLayer,
|
||||
trace::TraceLayer,
|
||||
};
|
||||
|
||||
use crate::auth;
|
||||
use crate::routes;
|
||||
use crate::state::AppState;
|
||||
use crate::{auth, routes, state::AppState};
|
||||
|
||||
/// Create the router with optional TLS configuration for HSTS headers
|
||||
pub fn create_router(state: AppState) -> Router {
|
||||
create_router_with_tls(state, None)
|
||||
create_router_with_tls(state, None)
|
||||
}
|
||||
|
||||
/// Create the router with TLS configuration for security headers
|
||||
pub fn create_router_with_tls(
|
||||
state: AppState,
|
||||
tls_config: Option<&pinakes_core::config::TlsConfig>,
|
||||
state: AppState,
|
||||
tls_config: Option<&pinakes_core::config::TlsConfig>,
|
||||
) -> Router {
|
||||
// Global rate limit: 100 requests/sec per IP
|
||||
let global_governor = Arc::new(
|
||||
GovernorConfigBuilder::default()
|
||||
.per_second(1)
|
||||
.burst_size(100)
|
||||
.finish()
|
||||
.unwrap(),
|
||||
);
|
||||
// Global rate limit: 100 requests/sec per IP
|
||||
let global_governor = Arc::new(
|
||||
GovernorConfigBuilder::default()
|
||||
.per_second(1)
|
||||
.burst_size(100)
|
||||
.finish()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
// Strict rate limit for login: 5 requests/min per IP
|
||||
let login_governor = Arc::new(
|
||||
GovernorConfigBuilder::default()
|
||||
// Strict rate limit for login: 5 requests/min per IP
|
||||
let login_governor = Arc::new(
|
||||
GovernorConfigBuilder::default()
|
||||
.per_second(12) // replenish one every 12 seconds
|
||||
.burst_size(5)
|
||||
.finish()
|
||||
.unwrap(),
|
||||
);
|
||||
);
|
||||
|
||||
// Rate limit for search: 10 requests/min per IP
|
||||
let search_governor = Arc::new(
|
||||
GovernorConfigBuilder::default()
|
||||
// Rate limit for search: 10 requests/min per IP
|
||||
let search_governor = Arc::new(
|
||||
GovernorConfigBuilder::default()
|
||||
.per_second(6) // replenish one every 6 seconds (10/min)
|
||||
.burst_size(10)
|
||||
.finish()
|
||||
.unwrap(),
|
||||
);
|
||||
);
|
||||
|
||||
// Rate limit for streaming: 5 requests per IP (very restrictive for concurrent streams)
|
||||
let stream_governor = Arc::new(
|
||||
GovernorConfigBuilder::default()
|
||||
// Rate limit for streaming: 5 requests per IP (very restrictive for
|
||||
// concurrent streams)
|
||||
let stream_governor = Arc::new(
|
||||
GovernorConfigBuilder::default()
|
||||
.per_second(60) // replenish slowly (one per minute)
|
||||
.burst_size(5) // max 5 concurrent connections
|
||||
.finish()
|
||||
.unwrap(),
|
||||
);
|
||||
);
|
||||
|
||||
// Login route with strict rate limiting
|
||||
let login_route = Router::new()
|
||||
.route("/auth/login", post(routes::auth::login))
|
||||
.layer(GovernorLayer::new(login_governor));
|
||||
// Login route with strict rate limiting
|
||||
let login_route = Router::new()
|
||||
.route("/auth/login", post(routes::auth::login))
|
||||
.layer(GovernorLayer::new(login_governor));
|
||||
|
||||
// Public routes (no auth required)
|
||||
let public_routes = Router::new()
|
||||
// Public routes (no auth required)
|
||||
let public_routes = Router::new()
|
||||
.route("/s/{token}", get(routes::social::access_shared_media))
|
||||
// Enhanced sharing: public share access
|
||||
.route("/shared/{token}", get(routes::shares::access_shared))
|
||||
|
|
@ -76,19 +78,19 @@ pub fn create_router_with_tls(
|
|||
.route("/health/live", get(routes::health::liveness))
|
||||
.route("/health/ready", get(routes::health::readiness));
|
||||
|
||||
// Search routes with enhanced rate limiting (10 req/min)
|
||||
let search_routes = Router::new()
|
||||
.route("/search", get(routes::search::search))
|
||||
.route("/search", post(routes::search::search_post))
|
||||
.layer(GovernorLayer::new(search_governor));
|
||||
// Search routes with enhanced rate limiting (10 req/min)
|
||||
let search_routes = Router::new()
|
||||
.route("/search", get(routes::search::search))
|
||||
.route("/search", post(routes::search::search_post))
|
||||
.layer(GovernorLayer::new(search_governor));
|
||||
|
||||
// Streaming routes with enhanced rate limiting (5 concurrent)
|
||||
let streaming_routes = Router::new()
|
||||
.route("/media/{id}/stream", get(routes::media::stream_media))
|
||||
.layer(GovernorLayer::new(stream_governor));
|
||||
// Streaming routes with enhanced rate limiting (5 concurrent)
|
||||
let streaming_routes = Router::new()
|
||||
.route("/media/{id}/stream", get(routes::media::stream_media))
|
||||
.layer(GovernorLayer::new(stream_governor));
|
||||
|
||||
// Read-only routes: any authenticated user (Viewer+)
|
||||
let viewer_routes = Router::new()
|
||||
// Read-only routes: any authenticated user (Viewer+)
|
||||
let viewer_routes = Router::new()
|
||||
.route("/health", get(routes::health::health))
|
||||
.route("/health/detailed", get(routes::health::health_detailed))
|
||||
.route("/media/count", get(routes::media::get_media_count))
|
||||
|
|
@ -240,8 +242,8 @@ pub fn create_router_with_tls(
|
|||
)
|
||||
.nest("/notes", routes::notes::routes());
|
||||
|
||||
// Write routes: Editor+ required
|
||||
let editor_routes = Router::new()
|
||||
// Write routes: Editor+ required
|
||||
let editor_routes = Router::new()
|
||||
.route("/media/import", post(routes::media::import_media))
|
||||
.route(
|
||||
"/media/import/options",
|
||||
|
|
@ -456,8 +458,8 @@ pub fn create_router_with_tls(
|
|||
)
|
||||
.layer(middleware::from_fn(auth::require_editor));
|
||||
|
||||
// Admin-only routes: destructive/config operations
|
||||
let admin_routes = Router::new()
|
||||
// Admin-only routes: destructive/config operations
|
||||
let admin_routes = Router::new()
|
||||
.route(
|
||||
"/config/scanning",
|
||||
put(routes::config::update_scanning_config),
|
||||
|
|
@ -496,43 +498,43 @@ pub fn create_router_with_tls(
|
|||
.route("/auth/sessions", get(routes::auth::list_active_sessions))
|
||||
.layer(middleware::from_fn(auth::require_admin));
|
||||
|
||||
// CORS: allow same-origin by default, plus the desktop UI origin
|
||||
let cors = CorsLayer::new()
|
||||
.allow_origin([
|
||||
"http://localhost:3000".parse::<HeaderValue>().unwrap(),
|
||||
"http://127.0.0.1:3000".parse::<HeaderValue>().unwrap(),
|
||||
"tauri://localhost".parse::<HeaderValue>().unwrap(),
|
||||
])
|
||||
.allow_methods([
|
||||
Method::GET,
|
||||
Method::POST,
|
||||
Method::PUT,
|
||||
Method::PATCH,
|
||||
Method::DELETE,
|
||||
])
|
||||
.allow_headers([header::CONTENT_TYPE, header::AUTHORIZATION])
|
||||
.allow_credentials(true);
|
||||
// CORS: allow same-origin by default, plus the desktop UI origin
|
||||
let cors = CorsLayer::new()
|
||||
.allow_origin([
|
||||
"http://localhost:3000".parse::<HeaderValue>().unwrap(),
|
||||
"http://127.0.0.1:3000".parse::<HeaderValue>().unwrap(),
|
||||
"tauri://localhost".parse::<HeaderValue>().unwrap(),
|
||||
])
|
||||
.allow_methods([
|
||||
Method::GET,
|
||||
Method::POST,
|
||||
Method::PUT,
|
||||
Method::PATCH,
|
||||
Method::DELETE,
|
||||
])
|
||||
.allow_headers([header::CONTENT_TYPE, header::AUTHORIZATION])
|
||||
.allow_credentials(true);
|
||||
|
||||
// Create protected routes with auth middleware
|
||||
let protected_api = Router::new()
|
||||
.merge(viewer_routes)
|
||||
.merge(search_routes)
|
||||
.merge(streaming_routes)
|
||||
.merge(editor_routes)
|
||||
.merge(admin_routes)
|
||||
.layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
auth::require_auth,
|
||||
));
|
||||
// Create protected routes with auth middleware
|
||||
let protected_api = Router::new()
|
||||
.merge(viewer_routes)
|
||||
.merge(search_routes)
|
||||
.merge(streaming_routes)
|
||||
.merge(editor_routes)
|
||||
.merge(admin_routes)
|
||||
.layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
auth::require_auth,
|
||||
));
|
||||
|
||||
// Combine protected and public routes
|
||||
let full_api = Router::new()
|
||||
.merge(login_route)
|
||||
.merge(public_routes)
|
||||
.merge(protected_api);
|
||||
// Combine protected and public routes
|
||||
let full_api = Router::new()
|
||||
.merge(login_route)
|
||||
.merge(public_routes)
|
||||
.merge(protected_api);
|
||||
|
||||
// Build security headers layer
|
||||
let security_headers = ServiceBuilder::new()
|
||||
// Build security headers layer
|
||||
let security_headers = ServiceBuilder::new()
|
||||
// Prevent MIME type sniffing
|
||||
.layer(SetResponseHeaderLayer::overriding(
|
||||
header::X_CONTENT_TYPE_OPTIONS,
|
||||
|
|
@ -564,32 +566,34 @@ pub fn create_router_with_tls(
|
|||
HeaderValue::from_static("default-src 'none'; frame-ancestors 'none'"),
|
||||
));
|
||||
|
||||
let router = Router::new()
|
||||
.nest("/api/v1", full_api)
|
||||
.layer(DefaultBodyLimit::max(10 * 1024 * 1024))
|
||||
.layer(GovernorLayer::new(global_governor))
|
||||
.layer(TraceLayer::new_for_http())
|
||||
.layer(cors)
|
||||
.layer(security_headers);
|
||||
let router = Router::new()
|
||||
.nest("/api/v1", full_api)
|
||||
.layer(DefaultBodyLimit::max(10 * 1024 * 1024))
|
||||
.layer(GovernorLayer::new(global_governor))
|
||||
.layer(TraceLayer::new_for_http())
|
||||
.layer(cors)
|
||||
.layer(security_headers);
|
||||
|
||||
// Add HSTS header when TLS is enabled
|
||||
if let Some(tls) = tls_config {
|
||||
if tls.enabled && tls.hsts_enabled {
|
||||
let hsts_value = format!("max-age={}; includeSubDomains", tls.hsts_max_age);
|
||||
let hsts_header = HeaderValue::from_str(&hsts_value).unwrap_or_else(|_| {
|
||||
HeaderValue::from_static("max-age=31536000; includeSubDomains")
|
||||
});
|
||||
// Add HSTS header when TLS is enabled
|
||||
if let Some(tls) = tls_config {
|
||||
if tls.enabled && tls.hsts_enabled {
|
||||
let hsts_value =
|
||||
format!("max-age={}; includeSubDomains", tls.hsts_max_age);
|
||||
let hsts_header =
|
||||
HeaderValue::from_str(&hsts_value).unwrap_or_else(|_| {
|
||||
HeaderValue::from_static("max-age=31536000; includeSubDomains")
|
||||
});
|
||||
|
||||
router
|
||||
.layer(SetResponseHeaderLayer::overriding(
|
||||
header::STRICT_TRANSPORT_SECURITY,
|
||||
hsts_header,
|
||||
))
|
||||
.with_state(state)
|
||||
} else {
|
||||
router.with_state(state)
|
||||
}
|
||||
router
|
||||
.layer(SetResponseHeaderLayer::overriding(
|
||||
header::STRICT_TRANSPORT_SECURITY,
|
||||
hsts_header,
|
||||
))
|
||||
.with_state(state)
|
||||
} else {
|
||||
router.with_state(state)
|
||||
router.with_state(state)
|
||||
}
|
||||
} else {
|
||||
router.with_state(state)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue